IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 10 - Assembler - Podmíněné a nepodmíněné skoky

V minulé lekci, Assembler - Další instrukce pro práci s čísly, jsme si ukázali násobení, dělení a také sčítání a odčítání velkých čísel.

V dnešním ASM tutoriálu se budeme věnovat podmíněným a nepodmíněným skokům.

Skoky se používají při programování podmínek nebo cyklů. V kompilátoru MASM můžeme pro podmínky a cykly používat příkazy .if, .repeat, .while. Kompilátor MASM za nás automaticky vygeneruje instrukce skoků. Naopak v kompilátoru NASM nejsou žádné podmínky nebo cykly a tuto funkcionalitu musíme simulovat pomocí skoků na různá návěstí v programu a samozřejmě pomocí porovnávacích instrukcí. Právě této problematice se budeme dnes věnovat.

Skoky v ASM jsou podmíněné nebo nepodmíněné.

Nepodmíněné skoky

Nepodmíněné skoky složí k tomu, abychom mohli neomezeně skákat z jedné části kódu do druhé. V našem Hello world programu jsme takto např. skákali na návěstí Return.

V praxi takto můžeme třeba vytvořit nekonečný cyklus nebo prostě přeskočit nějakou část kódu.

Instrukce JMP (JuMP)

Instrukci JMP již také známe, slouží právě k nepodmíněnému skoku. Má pouze jeden operand a tím je návěstí, které udává, kam máme skočit.

Návěstí deklarujeme přidáním : za vybraný název, např. takto:

sem_skoc:

Ve strojovém kódu samozřejmě nejsou žádná návěští, ale číselné adresy. Ty za nás vypočítává kompilátor. Pro krátké skoky vloží do instrukce 8, 16 nebo 32-bitovou relativní adresu. Ta je kladná pro skoky směrem dopředu a záporná pro skoky zpět. Pro dlouhé skoky se použije absolutní 16 nebo 32-bitovou adresa. Ve strojovém kódu proto existuje několik instrukcí skoku: E9, EA, EB, FF. V ASM jednoduše ve všech případech používáme JMP a kompilátor se podle délky skoku rozhodne, jaký kód instrukce použije.

Někdy je cíl skoku potřeba určit až za běhu programu. V takových případech se používají nepřímé skoky, u kterých je adresa skoku buď v registru, nebo někde v paměti.

Příklad

Nyní si ukážeme příklad nekonečného cyklu:

znovu:
mov si, hello_world_message
call print_string
jmp znovu

hello_world_message db 'Hello, World!', 13, 10, 0

Kód předpokládá, že máme někde deklarovanou i funkci print_string, kterou jsme si deklarovali již dříve v našem Hello world programu.

Deklarujeme si návěstí znovu: a do registru SI přesuneme text, který pomocí funkce print_string vypíšeme. Dále skočíme na návěstí znovu: a celé se to bude opakovat znovu, až do nekonečna. Samozřejmě nemusíme skočit dozadu, abychom takto skákali stále dokola, ale můžeme skočit i někam dopředu, dále do programu.

Výsledkem programu tedy bude:

Hello, World!
Hello, World!
Hello, World!
Hello, World!
...

Z tohoto nekonečného cyklu bychom za nějaké podmínky mohli vyskočit pryč. K tomu ale již potřebujeme skok nějak opodmínkovat.

Podmíněné skoky

Častěji budeme ale samozřejmě potřebovat skočit podmíněně. To znamená, že skočíme pouze tehdy, pokud bude splněna nějaká podmínka.

Instrukce CMP (CoMPare)

Před provedením samotného skoku je potřeba použít instrukci CMP (CoMPare). Ta má dva operandy (A a B) a defakto supluje za příkaz if z vyšších programovacích jazyků. CMP operandy porovná a na výsledek pak můžeme reagovat pomocí dalších instrukcí.

Celá podmínka se zapisuje takto:

cmp [první operand], [druhý operand]
j[x] [návěstí]

Pojďme si ukázat tabulku, na jaké výsledky porovnání můžeme pomocí kterých instrukcí reagovat:

Porovnání A == B A != B A > B A < B A >= B A <= B
Instrukce JE (JZ) JNE (JNZ) JA (JNBE) JB (JNAE) JNB (JAE) JNA (JBE)

Instrukce pro každou možnost jsou hned 2. Nenechte se zmást, důvod je prostý. Procesor reálně obě čísla porovnává tak, že je odečte. Můžeme se tedy ptát, zda jsou např. 2 čísla rovna nebo zda je jejich rozdíl 0. Obojí je to samé, jen má instrukce pro přehlednost jiný název, aby bylo z kódu jasné, co v dané chvíli myslíme.

Instrukce JE (Jump if Equal) a JZ (Jump if Zero)

Skočí na dané návěstí, pokud jsou porovnávaná čísla stejná (A == B).

Instrukce JNE (Jump if Not Equal) a JNZ (Jump if Not Zero)

Skočí na dané návěstí, pokud jsou porovnávaná čísla různá (A != B).

Instrukce JA (Jump if Above) a JNBE (Jump if Not Below or Equal)

Skočí na dané návěstí, pokud je první porovnávané číslo větší než druhé (A > B).

Instrukce JB (Jump if Below) a JNAE (Jump if Not Above or Equal)

Skočí na dané návěstí, pokud je první porovnávané číslo menší než druhé (A > B).

Instrukce JNB (Jump if Not Below) a JAE (Jump if Above or Equal)

Skočí na dané návěstí, pokud je první porovnávané číslo větší nebo rovno než druhé (A >= B).

Instrukce JNA (Jump if Not Above) a JBE (Jump if Below or Equal)

Skočí na dané návěstí, pokud je první porovnávané číslo menší nebo rovno než druhé (A <= B).

Pokud víte něco o reprezentaci čísel v počítači, tak čísla, která mohou mít i zápornou hodnotu, jsou v paměti uložena jinak (protože musí mít vymezenou paměť i pro záporné hodnoty, jsou posunutá o polovinu rozsahu). Toto má vliv na porovnávání čísel a ASM za nás problém neřeší. Operace pro skoky, které jsme si ukázali, platí pouze pro unsigned čísla (tedy ta, která mohou být jen nezáporná). Jak se s tím vypořádat u signed čísel si ukážeme příště.

Příklad

Určitě si teď zasloužíme příklad. Tady je:

mov ax, 1
mov bx, 2
cmp ax, bx
jb bx_je_vetsi
;v této části programu bx není větší
jmp konec
bx_je_vetsi:
;v této části programu je bx větší
konec:

Program skočí na dané návěstí podle toho, zda je hodnota 1 > 2.

Instrukce TEST

Nejčastěji se instrukce TEST používá pro zjištění, jestli je hodnota v registru kladná, záporná, nebo nulová.

Porovnání A == 0 A != 0 A < 0 A >= 0
Instrukce JZ JNZ JS JNS

Použití pak vypadá následovně:

test ax,ax
jz ax_je_nulove
js ax_je_zaporne

Pokud jsou operandy instrukce TEST různé, provede se logická operace AND. Tím můžeme testovat jednotlivé bity prvního operandu. Nejčastěji se testuje poslední bit, čímž se zjistí, jestli je číslo sudé nebo liché:

test ax,1
jz ax_je_sude
jnz ax_je_liche

Cykly

U všech typů cyklů můžeme používat kterékoli obecné registry. U for cyklů ale musíme dávat pozor, abychom nechtěně uvnitř cyklu nezměnili hodnotu registru, ve kterém máme řídící proměnnou cyklu.

Ukažme si, jak pomocí skoků nahradit různé druhy cyklů, které určitě znáte z vyšších programovacích jazyků.

Cyklus s podmínkou na konci (do-while):

muj_cyklus:
; Vnitřek cyklu
cmp ax,bx
ja muj_cyklus

Cyklus s podmínkou na začátku (while):

jmp porovnani
muj_cyklus:
; Vnitřek cyklu
porovnani:
cmp ax,bx
ja muj_cyklus

Cyklus od 10 do 1 (for):

mov cx, 10
muj_cyklus:
; Vnitřek cyklu
dec cx
jnz muj_cyklus

Cyklus od 1 do 10 (for):

mov cx, 1
muj_cyklus:
; Vnitřek cyklu
inc cx
cmp cx,10
jbe muj_cyklus

Ve starších programech se také často používala instrukce loop. Ta dělá úplně to samé jako dvě instrukce dec cx a jnz návěstí. Jediný rozdíl je v tom, že je asi čtyřikrát pomalejší. Druhá nevýhoda je v tom, že ji nelze použít s jiným registrem než cx.

V příští lekci, Assembler - Kombinace skoků a příznaky, se naučíme kombinovat podmíněné a nepodmíněné skoky, zjistíme, jak skokové instrukce získají výsledek z CMP a jak porovnávat signed čísla.


 

Předchozí článek
Assembler - Další instrukce pro práci s čísly
Všechny články v sekci
Základy assembleru
Přeskočit článek
(nedoporučujeme)
Assembler - Kombinace skoků a příznaky
Článek pro vás napsal Jakub Verner
Avatar
Uživatelské hodnocení:
3 hlasů
Autor se věnuje programování v x86 Assembleru.
Aktivity