Jak wiemy, łańcuch znaków to nic innego jak jednowymiarowa tablica bajtów. Dlatego podane tutaj informacje tak samo działają dla tablic.
W zestawie instrukcji procesora przeznaczonych jest klika rozkazów przeznaczonych specjalnie
do operacji na łańcuchach znaków: MOVS
, CMPS
, SCAS
,
LODS
, STOS
. To nimi właśnie teraz się zajmiemy.
Rozkazy te operują na łańcuchach spod DS:[SI/ESI/RSI] lub ES:[DI/EDI/RDI] lub obydwu. Rejestry segmentowe nie będą tutaj grać dużej roli bo pokazują zawsze na ten sam segment, więc będziemy je pomijać. Oraz, zajmiemy się omówieniem instrukcji tylko na ESI oraz EDI, pomijając rejestry 64-bitowe, dla których wszystko wygląda analogicznie.
Instrukcje występują w 4 formach: *B, *W, *D (dla 32-bitowych) i *Q (dla 64-bitowych). Operują one odpowiednio na bajtach, słowach, podwójnych słowach i danych 64-bitowych. Po każdym wykonaniu pojedynczej operacji zwiększają rejestry SI/ESI/RSI i DI/EDI/RDI o 1, 2, 4 lub 8, przechodząc tym samym na następne elementy.
UWAGA: Zwiększaniem rejestrów *SI i *DI steruje flaga kierunku DF: jeśli równa 0, oba rejestry są zwiększane, jeśli 1 - są zmniejszane o odpowiednią liczbę (co pozwala na przykład na przeszukiwanie łańcuchów wspak). Flagę DF można wyczyścić instrukcją CLD, a ustawić instrukcją STD.
Zasadą działania tej instrukcji jest przeniesienie odpowiedniej ilości bajtów spod DS:[SI] i umieszczenie ich pod ES:[DI]. Ale przeniesienie co najwyżej 4 bajtów to przecież żaden wysiłek:
mov eax, ds:[si] ; NASM/FASM: mov eax, [ds:si] mov es:[di], eax
Dlatego wymyślono prefiks REP
(powtarzaj). Jest on ważny tylko dla instrukcji operujących na
łańcuchach znaków oraz instrukcji INS
i OUTS
. Powoduje on powtórzenie
działania instrukcji (E)CX razy. Teraz już widać możliwości tej instrukcji. Chcemy przenieść
128 bajtów? Proszę 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
Oczywiście, dwie ostatnie linijki można było zastąpić czymś takim i też by podziałało:
mov cx, 32 rep movsd
Sposób drugi oczywiście jest lepszy, bo ma mniej operacji (choć najwięcej czasu i tak zajmuje
samo rozpędzenie się instrukcji REP
).
Instrukcji REP MOVS*
można używać do przenoszenia małej ilości danych.
Gdy ilości danych rosną, lepiej sprawują się MMX i SSE (patrz:
część 6).
Ta instrukcja porównuje odpowiednią liczbę bajtów spod DS:[SI] i ES:[DI]. Ale nas oczywiście nie
interesuje porównywanie pojedynczych ilości. Myślimy więc o prefiksie REP
, ale po chwili
zastanowienia dochodzimy do wniosku, że w ten sposób otrzymamy tylko wynik ostatniego porównania,
wszystkie wcześniejsze zostaną zaniedbane. Dlatego wymyślono prefiksy REPE/REPZ
(powtarzaj,
dopóki równe/flaga ZF ustawiona) oraz REPNE/REPNZ
(powtarzaj, dopóki nie równe/flaga ZF = 0).
Na przykład, aby sprawdzić równość dwóch łańcuchów, 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 bajtów maksymalnie chcemy porównać cld repe cmpsb ; dopóki są równe, porównuj dalej jnz lancuchy_nie_rowne
REPE
przestanie działać na pierwszych różniących się bajtach. W CX otrzymamy pewną liczbę.
Różnica liczby 256 i tej liczby mówi o ilości identycznych znaków i jednocześnie o tym, na
której pozycji znajdują się różniące się znaki.
Oczywiście, jeśli po ukończeniu REPE
rejestr CX=0, to znaczy że sprawdzono wszystkie znaki
(i wszystkie dotychczas były równe). Wtedy flagi mówią o ostatnim porównaniu.
REPE CMPS
ustawia flagi jak normalna instrukcja CMP
.
Ta instrukcja przeszukuje łańcuch znaków pod ES:[DI] w poszukiwaniu bajtu z AL, słowa z AX lub
podwójnego słowa z EAX. Służy to do szybkiego znalezienia pierwszego wystąpienia danego
elementu w łańcuchu.
Przykład: znaleźć pozycję pierwszego wystąpienia 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 ; dopóki sprawdzany znak różny ; 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 łańcuchu
REPNE
przestanie działać w dwóch przypadkach: CX=0 (wtedy nie znaleziono szukanego
elementu) oraz wtedy, gdy ZF=1 (gdy po drodze natrafiła na szukany element, wynik porównania
ustawił flagę ZF).
Instrukcje LODS*
pobierają do AL/AX/EAX odpowiednią liczbę bajtów spod DS:[SI]. Jak widać,
prefiksy REP*
nie mają tutaj sensu, bo w rejestrze docelowym i tak zawsze wyląduje ostatni
element.
Ale za to tej instrukcji można używać do pobierania poszczególnych znaków do dalszego
sprawdzania, na przykład
cld petla: lodsb ; pobierz kolejny znak cmp al, 13 jne nie_enter cmp al, "0" je al_to_zero .... loop petla
Instrukcja ta umieszcza AL/AX/EAX pod ES:[DI]. Poza oczywistym zastosowaniem, jakim jest na przykład
zapisywanie kolejnych podawanych przez użytkownika znaków gdzieś do tablicy, STOS
można też
użyć do szybkiej inicjalizacji tablicy w czasie działania programu lub do wyzerowania pamięci:
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
Wyrażenia regularne (regular expressions, regex) to po prostu ciągi znaczków,
przy użyciu których możemy opisywać dowolne
łańcuchy znaków (adresy e-mail,
WWW, nazwy plików z pełnymi ścieżkami, ...).
Na wyrażenie regularne składają się różne symbole. Postaram się je teraz po krótce omówić.
aaa(dowolny ciąg znaków) - reprezentuje wszystkie łańcuchy, które go zawierają, na przykład
laaaaaaaaaato.
^asemblerreprezentuje wszystkie linie, które zaczynają się od ciągu znaków
asembler. Innymi słowy, każda linia zaczynająca się od
asemblerpasuje do tego wyrażenia.
asm$reprezentuje wszystkie linie, które kończą się na
asm.
^a.m$reprezentuje linie, które zawierają w sobie tylko a*m, gdzie gwiazdka to dowolny znak (w tym cyfry). Do tego wzorca pasują
asm,
aim,
a0mi wiele innych.
a|b|zreprezentuje dowolną z tych trzech liter i żadną inną.
^(aa)|(bb)|(asm)reprezentuje linie, które zaczynają się od
aa,
bblub
asm.
k,
alub
jpasują do wzorca
[ajk].*. Można tutaj podawać przedziały znaków - wtedy 2 skrajne znaki oddzielamy myślnikiem, na przykład
[a-z]. Umieszczenie w środku znaku daszka ^ oznacza przeciwieństwo, na przykład
[^0-9]reprezentuje znaki, które nie są cyfrą (a tym samym wszystkie ciągi nie zawierające cyfr).
ko?treprezentuje wyrazy
koti
kt, ale już nie
koot.
ko*treprezentuje więc wyrazy
kt,
kot,
koot,
kooot, itd.
al(fa)+reprezentuje
alfa,
alfafa,
alfafafaitd, ale nie
al.
[0-9]{7}reprezentuje więc dowolny ciąg składający się dokładnie z 7 cyfr.
[a-z]{2,}reprezentuje więc dowolny ciąg znaków składający się co najmniej z 2 małych liter.
[A-M]{3,7}reprezentuje dowolny ciąg znaków składający się z co najmniej 3 i co najwyżej 7 wielkich liter z przedziału od A do M.
\.
Dalsze przykłady:
0xalbo (do wyboru) przyrostkiem
Hlub
h.