   #Start Prev Next Contents

   Jak pisac programy w jezyku asembler?

Czesc 16 - Operacje na lancuchach znakow. Wyrazenia regularne

   Jak wiemy, lancuch znakow to nic innego jak jednowymiarowa tablica
   bajtow. Dlatego podane tutaj informacje tak samo dzialaja dla tablic.

   W zestawie instrukcji procesora przeznaczonych jest klika rozkazow
   przeznaczonych specjalnie do operacji na lancuchach znakow: MOVS,
   CMPS, SCAS, LODS, STOS. To nimi wlasnie teraz sie zajmiemy.

   Rozkazy te operuja na lancuchach spod DS:[SI/ESI/RSI] lub
   ES:[DI/EDI/RDI] lub obydwu. Rejestry segmentowe nie beda tutaj grac
   duzej roli bo pokazuja zawsze na ten sam segment, wiec bedziemy je
   pomijac. Oraz, zajmiemy sie omowieniem instrukcji tylko na ESI oraz
   EDI, pomijajac rejestry 64-bitowe, dla ktorych wszystko wyglada
   analogicznie.

   Instrukcje wystepuja w 4 formach: *B, *W, *D (dla 32-bitowych) i *Q
   (dla 64-bitowych). Operuja one odpowiednio na bajtach, slowach,
   podwojnych slowach i danych 64-bitowych. Po kazdym wykonaniu
   pojedynczej operacji zwiekszaja rejestry SI/ESI/RSI i DI/EDI/RDI o 1,
   2, 4 lub 8, przechodzac tym samym na nastepne elementy.

   UWAGA: Zwiekszaniem rejestrow *SI i *DI steruje flaga kierunku DF:
   jesli rowna 0, oba rejestry sa zwiekszane, jesli 1 - sa zmniejszane o
   odpowiednia liczbe (co pozwala na przyklad na przeszukiwanie lancuchow
   wspak). Flage DF mozna wyczyscic instrukcja CLD, a ustawic instrukcja
   STD.
     _________________________________________________________________

MOVS

   (przeskocz MOVS)

   Zasada dzialania tej instrukcji jest przeniesienie odpowiedniej ilosci
   bajtow spod DS:[SI] i umieszczenie ich pod ES:[DI]. Ale przeniesienie
   co najwyzej 4 bajtow to przeciez zaden wysilek:
        mov     eax, ds:[si]            ; NASM/FASM: mov eax, [ds:si]
        mov     es:[di], eax

   Dlatego wymyslono prefiks REP (powtarzaj). Jest on wazny tylko dla
   instrukcji operujacych na lancuchach znakow oraz instrukcji INS i
   OUTS. Powoduje on powtorzenie dzialania instrukcji (E)CX razy. Teraz
   juz widac mozliwosci tej instrukcji. Chcemy przeniesc 128 bajtow?
   Prosze bardzo:
        mov     ax, seg zrodlo
        mov     ds, ax
        mov     si, offset zrodlo       ; NASM/FASM: mov si, zrodlo

        mov     ax, seg cel
        mov     es, ax
        mov     di, offset cel          ; NASM/FASM: mov di, cel

        cld                             ; sprawdzaj do przodu
        mov     cx, 128
        rep     movsb

   Oczywiscie, dwie ostatnie linijki mozna bylo zastapic czyms takim i
   tez by podzialalo:
        mov     cx, 32
        rep     movsd

   Sposob drugi oczywiscie jest lepszy, bo ma mniej operacji (choc
   najwiecej czasu i tak zajmuje samo rozpedzenie sie instrukcji REP).

   Instrukcji REP MOVS* mozna uzywac do przenoszenia malej ilosci danych.
   Gdy ilosci danych rosna, lepiej sprawuja sie MMX i SSE (patrz: czesc
   6).
     _________________________________________________________________

CMPS

   (przeskocz CMPS)

   Ta instrukcja porownuje odpowiednia liczbe bajtow spod DS:[SI] i
   ES:[DI]. Ale nas oczywiscie nie interesuje porownywanie pojedynczych
   ilosci. Myslimy wiec o prefiksie REP, ale po chwili zastanowienia
   dochodzimy do wniosku, ze w ten sposob otrzymamy tylko wynik
   ostatniego porownania, wszystkie wczesniejsze zostana zaniedbane.
   Dlatego wymyslono prefiksy REPE/REPZ (powtarzaj, dopoki rowne/flaga ZF
   ustawiona) oraz REPNE/REPNZ (powtarzaj, dopoki nie rowne/flaga ZF =
   0).
   Na przyklad, aby sprawdzic rownosc dwoch lancuchow, zrobimy tak:
        mov     ax, seg lancuch1
        mov     ds, ax
        mov     si, offset lancuch1     ; NASM/FASM: mov si, lancuch1

        mov     ax, seg lancuch2
        mov     es, ax
        mov     di, offset lancuch2     ; NASM/FASM mov di, lancuch2

        mov     cx, 256         ; tyle bajtow maksymalnie chcemy porownac
        cld
        repe    cmpsb                   ; dopoki sa rowne, porownuj dalej
        jnz     lancuchy_nie_rowne

   REPE przestanie dzialac na pierwszych rozniacych sie bajtach. W CX
   otrzymamy pewna liczbe. Roznica liczby 256 i tej liczby mowi o ilosci
   identycznych znakow i jednoczesnie o tym, na ktorej pozycji znajduja
   sie rozniace sie znaki.
   Oczywiscie, jesli po ukonczeniu REPE rejestr CX=0, to znaczy ze
   sprawdzono wszystkie znaki (i wszystkie dotychczas byly rowne). Wtedy
   flagi mowia o ostatnim porownaniu.
   REPE CMPS ustawia flagi jak normalna instrukcja CMP.
     _________________________________________________________________

SCAS

   (przeskocz SCAS)

   Ta instrukcja przeszukuje lancuch znakow pod ES:[DI] w poszukiwaniu
   bajtu z AL, slowa z AX lub podwojnego slowa z EAX. Sluzy to do
   szybkiego znalezienia pierwszego wystapienia danego elementu w
   lancuchu.
   Przyklad: znalezc pozycje pierwszego wystapienia litery "Z" w zmiennej
   "lancuch1":
                mov     ax, seg lancuch1
                mov     es, ax

                mov     al, "Z"         ; poszukiwany element
                mov     di, lancuch1
                mov     cx, 256
                cld
                repne   scasb           ; dopoki sprawdzany znak rozny
                                        ; od "Z", szukaj dalej

                je      znaleziono

                mov     di, -1          ; gdy nie znaleziono,
                                        ; zwracamy -1
                jmp     koniec

        znaleziono:
                sub     di, lancuch1    ; DI = pozycja znalezionego
                                        ; znaku w lancuchu

   REPNE przestanie dzialac w dwoch przypadkach: CX=0 (wtedy nie
   znaleziono szukanego elementu) oraz wtedy, gdy ZF=1 (gdy po drodze
   natrafila na szukany element, wynik porownania ustawil flage ZF).
     _________________________________________________________________

LODS

   (przeskocz LODS)

   Instrukcje LODS* pobieraja do AL/AX/EAX odpowiednia liczbe bajtow spod
   DS:[SI]. Jak widac, prefiksy REP* nie maja tutaj sensu, bo w rejestrze
   docelowym i tak zawsze wyladuje ostatni element.
   Ale za to tej instrukcji mozna uzywac do pobierania poszczegolnych
   znakow do dalszego sprawdzania, na przyklad
                cld
        petla:
                lodsb                           ; pobierz kolejny znak

                cmp     al, 13
                jne     nie_enter

                cmp     al, "0"
                je      al_to_zero

                ....

                loop    petla
     _________________________________________________________________

STOS

   (przeskocz STOS)

   Instrukcja ta umieszcza AL/AX/EAX pod ES:[DI]. Poza oczywistym
   zastosowaniem, jakim jest na przyklad zapisywanie kolejnych podawanych
   przez uzytkownika znakow gdzies do tablicy, STOS mozna tez uzyc do
   szybkiej inicjalizacji tablicy w czasie dzialania programu lub do
   wyzerowania pamieci:
                mov     ax, seg tablica
                mov     es, ax
                mov     di, offset tablica ; NASM/FASM: mov di, tablica

                mov     eax, 11223344h
                mov     cx, 1000
                cld
                rep     stosd
                ...

        tablica  dd 1000 dup(0)         ; NASM/FASM:
                                        ; tablica: TIMES 1000 dd 0
     _________________________________________________________________

Wyrazenia regularne

   Wyrazenia regularne (regular expressions, regex) to po prostu ciagi
   znaczkow, przy uzyciu ktorych mozemy opisywac dowolne lancuchy znakow
   (adresy e-mail, WWW, nazwy plikow z pelnymi sciezkami, ...).
   Na wyrazenie regularne skladaja sie rozne symbole. Postaram sie je
   teraz po krotce omowic.
     * "aaa" (dowolny ciag znakow) - reprezentuje wszystkie lancuchy,
       ktore go zawieraja, na przyklad "laaaaaaaaaato".
     * ^ - oznacza poczatek linii (wiersza). Na przyklad wyrazenie
       "^asembler" reprezentuje wszystkie linie, ktore zaczynaja sie od
       ciagu znakow "asembler". Innymi slowy, kazda linia zaczynajaca sie
       od "asembler" pasuje do tego wyrazenia.
     * $ - oznacza koniec linii. Na przyklad wyrazenie "asm$"
       reprezentuje wszystkie linie, ktore koncza sie na "asm".
     * . (kropka) - dowolny znak (z wyjatkiem znaku nowej linii). Na
       przyklad wyrazenie "^a.m$" reprezentuje linie, ktore zawieraja w
       sobie tylko a*m, gdzie gwiazdka to dowolny znak (w tym cyfry). Do
       tego wzorca pasuja "asm", "aim", "a0m" i wiele innych.
     * | (Shift+BackSlash)- oznacza alternatywe. Na przyklad wyrazenie
       "a|b|z" reprezentuje dowolna z tych trzech liter i zadna inna.
     * (,) - nawiasy sluza do grupowania wyrazow. Na przyklad
       "^(aa)|(bb)|(asm)" reprezentuje linie, ktore zaczynaja sie od
       "aa", "bb" lub "asm".
     * [,] - wyznaczaja klase znakow. Na przyklad wszystkie wyrazy
       zaczynajace sie od "k", "a" lub "j" pasuja do wzorca "[ajk].*".
       Mozna tutaj podawac przedzialy znakow - wtedy 2 skrajne znaki
       oddzielamy myslnikiem, na przyklad "[a-z]". Umieszczenie w srodku
       znaku daszka ^ oznacza przeciwienstwo, na przyklad "[^0-9]"
       reprezentuje znaki, ktore nie sa cyfra (a tym samym wszystkie
       ciagi nie zawierajace cyfr).
     * ? - oznacza co najwyzej jedno wystapienie poprzedzajacego znaku
       lub grupy. Na przyklad, "ko?t" reprezentuje wyrazy "kot" i "kt",
       ale juz nie "koot".
     * * - oznacza dowolna liczbe wystapien poprzedzajacego znaku/grupy.
       Wyrazenie "ko*t" reprezentuje wiec wyrazy "kt", "kot", "koot",
       "kooot", itd.
     * + - oznacza co najmniej jedno wystapienie poprzedzajacego
       znaku/grupy. Na przyklad "al(fa)+" reprezentuje "alfa", "alfafa",
       "alfafafa" itd, ale nie "al".
     * {n} - oznacza dokladnie n wystapien poprzedzajacego znaku/grupy.
       Wyrazenie "[0-9]{7}" reprezentuje wiec dowolny ciag skladajacy sie
       dokladnie z 7 cyfr.
     * {n,} - oznacza co najmniej n wystapien poprzedzajacego
       znaku/grupy. Wyrazenie "[a-z]{2,}" reprezentuje wiec dowolny ciag
       znakow skladajacy sie co najmniej z 2 malych liter.
     * {n,m} - oznacza co najmniej n i co najwyzej m wystapien
       poprzedzajacego znaku/grupy. Wiec wyrazenie "[A-M]{3,7}"
       reprezentuje dowolny ciag znakow skladajacy sie z co najmniej 3 i
       co najwyzej 7 wielkich liter z przedzialu od A do M.
     * Jesli w lancuchu moze wystapic ktorys ze znakow specjalnych,
       nalezy go w wyrazeniu poprzedzic odwrotnym ukosnikiem "\".

   Dalsze przyklady:
     * ([a-zA-Z0-9]+\.?)+[a-zA-Z]+@([a-zA-Z0-9]+\.)+[a-zA-Z]{2,4} - adres
       e-mail (zapisany tak, by login ani domena nie konczyly sie kropka)
     * ([a-zA-Z]{3,6}://)?([a-zA-Z0-9/\-]+\.?)+[a-zA-Z0-9]+(#[a-zA-Z0-9\-
       ]+)? - adres (z protokolem lub bez) zasobu na serwerze (zapisany
       tak, by nie konczyl sie kropka, moze zawierac myslniki a w
       ostatnim czlonie takze znak #)

   Poprzednia czesc kursu (klawisz dostepu 3)
   Kolejna czesc kursu (klawisz dostepu 4)
   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
     _________________________________________________________________

Cwiczenia

    1. Napisac program zawierajacy 2 tablice DWORDow o wymiarach 17 na
       31, po czym w trakcie dzialania programu wypelnic kazde pole
       pierwszej wartoscia FFEEDDCCh. Potem, 8 pierwszych elementow
       skopiowac do drugiej tablicy, a reszte drugiej wypelnic wartoscia
       BA098765h. Wtedy porownac zawartosc obu tablic i wyliczyc pierwsza
       pozycje, na ktorej sie roznia (powinna oczywiscie wynosic 9)
    2. Napisac wyrazenie regularne, ktore opisze:
          + wszystkie wyrazenia deklaracji zmiennych: DB, DW, DP, DQ, DT
          + znacznik HTML bez atrybutow, czyli cos wygladajace tak: < PRE
            > lub tak: < /LI > (bez spacji).
          + liczbe szesnastkowa dowolnej niezerowej dlugosci z
            ewentualnym przedrostkiem "0x" albo (do wyboru) przyrostkiem
            "H" lub "h".
