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:[ESI/RSI] lub ES:[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 [ESI] i umieszczenie ich pod [EDI]. Ale przeniesienie co najwyżej 4 bajtów to przecież żaden wysiłek:
mov eax, [esi] mov [edi], 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 ECX razy. Teraz już widać możliwości tej instrukcji. Chcemy przenieść 128 bajtów? Proszę
bardzo:
mov esi, zrodlo mov edi, cel cld ; idź do przodu mov ecx, 128 rep movsb
Oczywiście, dwie ostatnie linijki można było zastąpić czymś takim i też by podziałało:
mov ecx, 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 [ESI] i [EDI]. 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 esi, lancuch1 mov edi, lancuch2 mov ecx, 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 ECX 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 ECX=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 [EDI] 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 łańcuchu lancuch1
:
mov al, "Z" ; poszukiwany element mov edi, lancuch1 mov ecx, 256 cld repne scasb ; dopóki sprawdzany znak różny ; od "Z", szukaj dalej je znaleziono mov edi, -1 ; gdy nie znaleziono, zwracamy -1 jmp koniec znaleziono: sub edi, lancuch1 ; EDI = pozycja znalezionego znaku w łańcuchu
REPNE
przestanie działać w dwóch przypadkach: ECX=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 [ESI]. 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 [EDI]. 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 edi, tablica mov eax, 11223344h mov ecx, 1000 cld rep stosd ... 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.