Výuka assembleru

4. Porovnávací instrukce a instrukce skoků

<< Předchozí díl
Další díl >>

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.

Porovnáváme ...

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

... a skáčeme

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

Skáčeme jenom občas

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

<< Předchozí díl
Další díl >>