Výuka assembleru
4. Porovnávací instrukce a instrukce skoků
Porovnávací instrukce jsou v assembleru obdobou strukturovaných příkazů if-else, for, while nebo do-while. V instrukční sadě procesorů x86 není téměř žádná přímá podpora takovýchto příkazů, vše se provádí přes vhodně napsané instrukce podmíněných nebo nepodmíněných skoků, často ve spojení s porovnávacími instrukcemi.
K porovnávání dvou hodnot stejné velikosti slouží instrukce cmp (compare). Tato instrukce srovnává dvě hodnoty - dva operandy. Porovnávání probíhá provedením fiktivní operace odčítání (Fiktivní proto, že výsledek odčítání se nikam neukládá). Syntaxe instrukce je cmp operand1, operand2, tedy např.
cmp eax, ebx ; Porovná hodnoty vložené v registrech EAX a EBX cmp ebx, 0 ; Porovná hodnotu registru EBX na nulu cmp eax, [ebx] ; Porovná hodnotu uloženou v registru EAX s hodnotou ; v paměti, jejíž hodnota je uložena v EBX
Operace porovnání provede nastavení některých příznaků v registru EFLAGS. Pro nás budou nejdůležitější tyto příznaky:
Příznak | Nastaven, pokud | Vynulován, pokud |
---|---|---|
CY (Carry flag) | Operand2 > Operand1 | Operand2 <= Operand1 |
ZR (Zero flag) | Operand2 == Operand1 | Operand2 != Operand1 |
PL | Nejvyšší bit výsledku je nastaven (záporné číslo) |
Nejvyšší bit výsledku je vynulován (kladné číslo) |
Výsledek porovnávání je možné využít např. instrukcemi adc a sbc, pro nás nejčastější využití bude ve spojení s podmíněnými instrukcemi skoků (viz. dále).
Instrukce skoků mohou být nepodmíněné (budou vykonány vždy) nebo podmíněné (budou vykonány pouze v určitém případě). Pro provedení instrukce skoku potřebujeme navíc definovat tzv. návěští (label). V programu to vypadá např. takto:
mov ecx, 0 @@LoopStart: ; Návěští - začátek cyklu add eax, eax ; Instrukce uvnitř cyklu jmp @@LoopStart ; Skok na začátek cyklu
Pro nepodmíněný skok používáme instrukci jmp (jump), která provede nepodmíněný skok na navěští, které je uvedeno jako parametr instrukce jmp. Návěští musí začínat písmenem, podtržítkem nebo znakem '@' a může obsahovat písmena, čísla a znaky podtržítko nebo '@'. Někdy bývá zvykem, že návěští začínají např. dvěma znaky '@'. Při kompilaci instrukce skoku zjistí překladač adresu první instrukce, která je za návěštím a adresu první instrukce za instrukcí skoku a obě hodnoty odečte. Získá tak relativní adresu skoku - ne tedy přímo adresu, kam se má skočit, ale rozdíl o kolik bytů se má skočit. Pokud se podíváme na předchozí příklad v okně Dissassembly a navíc zvolíme zobrazování bytů kódu (pravé tlačítko myši - položka "Code Bytes"), uvidíme za instrukcí hodnoty EB FC:
45: jmp @@LoopStart ; Skok na začátek cyklu 0040100A EB FC jmp @@LoopStart (00401008)
EB je kód instrukce jmp, FC znamená relativní skok o -4 byty (0FCh může být použito jako bezznaménkové číslo 252 nebo jako číslo se znaménkem -4).
Pro programování cyklů, podmínek, nebo různých algoritmů potřebujeme mít možnost vykonat podmíněné instrukce, tedy takové instrukce, které budou provedeny pouze pokud je splněna určitá podmínka. V asembleru se vykonání podmíněného bloku instrukcí provádí skokem na adresu, je-li nastaven bit v registru EFLAGS. Seznam pro nás nejdůležitějších instrukcí podmíněného skoku uvádí následující tabulka:
Instrukce | Podmínka skoku | V praxi |
---|---|---|
jz (Jump if Zero) je (Jump if Equal) |
ZF == 1 | Výsledek aritmetické operace je nula Hodnoty operandů instrukce cmp jsou shodné |
jnz (Jump if Not Zero) jne (Jump if Not Equal) |
ZF == 0 | Výsledek aritmetické operace není nula Hodnoty operandů instrukce cmp nejsou shodné |
jc (Jump if Carry) | CF == 1 | Přetečení při aritmetické operaci Operand 2 je větší než operand 1 |
jnc (Jump if Not Carry) | CF == 0 | Nenastalo přetečení při aritmetické operaci Operand 2 je menší nebo roven operandu 1 |
ja (Jump if Above) | CF == 0 a ZF == 0 | Operand1 je větší než Operand2 |
jna (Jump if Not Above) | CF == 1 nebo ZF == 1 | Operand1 není větší než Operand2 |
jb (Jump if Bellow) | CF == 1 a ZF == 0 | Operand1 je menší než Operand2 |
jnb (Jump if Not Bellow) | CF == 0 nebo ZF == 1 | Operand1 je není menší než Operand2 |
jcxz (Jump if CX is Zero) jecxz (Jump if ECX is Zero) |
CX == 0 ECX == 0 |
Hodnota registru CX je rovna nule Hodnota registru CX není rovna nule |
Několik příkladů pro úplné pochopení tabulky:
mov eax, 3 cmp eax, 0 ; Je hodnota EAX rovna nule ? je @@EAXIsZero ; Pokud ano, provede se skok sub eax, 4 ; Odečteme 4 od hodnoty v EAX jc @@Overflow ; Pokud nastalo přetečení, skok mov eax, 6 cmp eax, 5 ; Je hodnota v EAX > 5 ? ja @@EAXAbove5 ; Pokud ano, provede se skok mov ecx, eax sub ecx, ecx ; Odečteme ECX od ECX jecxz @@ECXIsZero ; Pokud je v ECX nula, skok. mov ebx, ecx cmp ebx, ecx ; Je EBX > ECX ? ja @@EBXAboveECX ; Pokud ano, skok @@EAXIsZero: @@Overflow: @@EAXAbove5:
Na závěr si ukážeme, jak spočítat desátou mocninu ze dvou s použitím instrukcí skoku:
mov eax, 2 ; Mocněnec mov ecx, 1 ; Počitadlo mocniny (v EAX je první mocnina dvou) @@DoPower: add eax, eax ; Provede umocnění (násobení EAX dvěma) jc @@PowerError ; Pokud došlo k přetečení, nelze spočítat mocninu inc ecx ; Zvýšíme mocninu cmp ecx, 10 ; je to desátá mocnina ? jne @@DoPower ; Zatím ne - opakuj cyklus @@PowerOK: ; EAX nyní obsahuje desátou mocninu dvou ; (ověřte programem calc.exe) @@PowerError: ; Chyba, mocnina se nevejde do registru EAX
Copyright Ladislav Zezula 2003