Jak pisać programy w języku asembler?

Część 16 - Operacje na łańcuchach znaków. Wyrażenia regularne

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.




MOVS


(przeskocz MOVS)

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).




CMPS


(przeskocz CMPS)

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.




SCAS


(przeskocz SCAS)

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).




LODS


(przeskocz LODS)

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



STOS


(przeskocz STOS)

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

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ć.

Dalsze przykłady:



Poprzednia część kursu (Alt+3)
Kolejna część kursu (Alt+4)
Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)

Ćwiczenia

  1. Napisać program zawierający 2 tablice DWORDów o wymiarach 17 na 31, po czym w trakcie działania programu wypełnić każde pole pierwszej wartością FFEEDDCCh. Potem, 8 pierwszych elementów skopiować do drugiej tablicy, a resztę drugiej wypełnić wartością BA098765h. Wtedy porównać zawartość obu tablic i wyliczyć pierwszą pozycję, na której się różnią (powinna oczywiście wynosić 9)

  2. Napisać wyrażenie regularne, które opisze: