2.5 Pętle

W językach wysokiego poziomu wyróżnia się kilka typów pętli takich jak FOR, WHILE itp. W asemblerze nie ma zdefiniowanego praktycznie żadnego typu pętli. Dzieje się tak ponieważ po przez odpowiednią organizację instrukcji skoków i operowaniu na liczniku można skonstruować dowolną pętlę nawet taką, która nie mieści się w żadnej konwencji znanej z języków wysokiego poziomu. Standardowym rejestrem odpowiedzialnym za zliczanie ilości przejść pętli jest rejestr CX. Można jednak do tego celu użyć każdego innego rejestru. Przykładową pętlę można już skonstruować na bazie dotychczas przedstawionych instrukcji.

org 0x100
 
	mov	cx,	3
 
start:
	mov	dx,	msg_loop_step
	mov	ah,	9
	int	0x21
 
	sub	cx,	1
	jnz	start
 
	mov	ax,	0x4C00
	int	0x21
 
msg_loop_step	db	"Kolejny krok petli. $"

Ten przykładowy program wykona wyświetlenie 3 razy na ekranie tekstu "Kolejny krok petli. ". Jako licznika użyłem rejestru CX wpisując na początku do niego wartość 3. Po kodzie odpowiedzialnym za wypisanie napisu znajduje się zmniejszenie wartości rejestru CX o jeden, a następnie sprawdzenie czy jego wartość wynosi zero. Jeżeli nie to wykonywany jest skok do etykiety start i kolejne wykonanie kroku pętli i tak aż do osiągnięcia przez rejestr CX wartości 0. Można ulepszyć ten program zamieniając instrukcję "sub cx, 1" na "dec cx". Instrukcja dec przyjmuje jeden argument i w wyniku zmniejsza jego wartość o jeden. Jest to instrukcja, której rozmiar to tylko jeden bajt tak więc w wyniku otrzymujemy mniejszy program. Dla porównania podaje wynik deasemblacji:

AX=09FF BX=0000 CX=0003 DX=0116 SP=FFFE BP=0000 SI=0000 DI=0000 DS=0747 ES=0747 SS=0747 CS=0747 IP=010A NV UP EI PL NZ NA PO NC 0747:010A 81E90100 SUB CX,0001 AX=09FF BX=0000 CX=0002 DX=0116 SP=FFFE BP=0000 SI=0000 DI=0000 DS=0747 ES=0747 SS=0747 CS=0747 IP=010A NV UP EI PL NZ NA PO NC 0747:010A 49 DEC CX

Dodatkowo w przypadku gdy używamy rejestru CX jako licznika o zmniejszającej się wartości możemy zastosować zamiennie instrukcję loop. Instrukcja ta zastępuje zestaw instrukcji zmniejszającej i instrukcji skoku jednak operuje jedynie na rejestrze CX.

org 0x100
 
	mov	cx,	3
 
start:
	mov	dx,	msg_loop_step
	mov	ah,	9
	int	0x21
 
	loop	start
 
	mov	ax,	0x4C00
	int	0x21
 
msg_loop_step	db	"Kolejny krok petli. $"

Za pomocą tej prostej pętli możemy napisać algorytm wypisywania na ekran ciągu znaków o zadanej długości.

org 0x100
 
	mov	bx,	hello_world
	mov	cx,	13
 
print:
	mov	ah,	2
	mov	dl,	[ds:bx]
	int	0x21
	inc	bx
	loop	print
 
	mov	ax,	0x4C00
	int	0x21
 
hello_world	db	"Hello world !"

Ten program wypisuje na ekran ciąg 13 znaków umieszczonych w pamięci od adresu ds:bx co w programie oznaczone jest etykietą hello_world.
Ciekawym może być wynik działania następnego programu.

org 0x100
 
	mov	bx,	0
	mov	cx,	0x100
 
print:
	mov	ah,	2
	mov	dl,	[ds:bx]
	int	0x21
	inc	bx
	loop	print
 
	mov	ax,	0x4C00
	int	0x21

Program ten spowoduje wypisane na ekran 256 znaków znajdujących się na początku segmentu w którym został umieszczony nasz program. Jak wcześniej wspominałem program jest umieszczony pod adresem 0x100 względem początku segmentu tak więc powyższy program spowoduje wypisanie w postaci znaków na ekranie zawartości pamięci poprzedzającej program. Oczywiście w tej całej strukturze 256 bajtów większość nie była zakodowana jako znaki więc powinniśmy zobaczyć dużą ilość tak zwanych krzaków jak to było w moim przypadku widać na poniższym obrazku.

Używając dodatkowo instrukcji skoku bezwarunkowego możemy zmodyfikować ten program tak aby napisać algorytm wypisywania na ekran ciągu znaków o dowolnym znaku oznaczającym koniec.

org 0x100
 
	mov	bx,	hello_world
 
print:
	mov	ah,	2
	mov	dl,	[ds:bx]
	cmp	dl,	0
	jz	end
	int	0x21
	inc	bx
	jmp	print
end:
 
	mov	ax,	0x4C00
	int	0x21
 
hello_world	db	"Hello world !",0

W tym przypadku jako koniec ciągu przyjąłem bajt o wartości 0. W języku C jako koniec ciągu znaków przyjmuje się właśnie bajt o wartości 0, a nie znak "$" tak jak to miało miejsce w DOS'ie. Tak więc powyższy kod przystosowuje wyświetlanie ciągów znaków do formy przyjętej w języku C.

FOR

Przedstawię teraz asemblerowy przykład realizacji pętli FOR znanej z języka C.

C (FOR)
for (int i=0; i<100; i++) { ... }
ASM (FOR)
	mov	cx,	0	; i=0
_for:				; {
	cmp	cx,	100	; i<100
	jae	_end_for
 
	...
 
	inc	cx		; i++
	jmp	_for
_end_for:			; }

Jako że rejestr CX jest użyty jako iterator to nie można wewnątrz pętli przypisać mu innego przeznaczenia. Jeżeli jednak będziemy chcieli mieć możliwość modyfikowania zawartości rejestru CX to trzeba jego wartość przechować na stosie na czas wykonywania kodu pętli.

ASM (FOR)
	mov	cx,	0	; i=0
_for:				; {
	cmp	cx,	100	; i<100
	jae	_end_for
	push	cx
 
	...
 
	pop	cx
	inc	cx		; i++
	jmp	_for
_end_for:			; }

LOOP

Instrukcja Loop ma dość małą maksymalną rozpiętość skoku. Wykonuje ona skok względny do adresu oddalonego o maksymalnie -128 do +127 bajtów. Jednak w przypadku pisania dość krótkich pętli sprawdzi się ona bardzo dobrze. W pozostałych przypadkach trzeba będzie rozpisać ją na parę instrukcji DEC i JNZ