BLACK FRIDAY! Slevy až 80 % jsou všude. Tak je nepropásni a přejdi do rostoucího IT oboru!
The real BF 2020

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

V minulé lekci, Assembler - Instrukce pro práci s čísly, jsme si probrali instrukce pro práci s čísly.

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!
...
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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 - Instrukce pro práci s čísly
Všechny články v sekci
Základy assembleru
Článek pro vás napsal Jakub Verner
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Autor se věnuje programování v Assembleru a v C#. Rád se zlepšuje, rozšiřuje si znalosti a věří, že když člověk chce, dokáže cokoliv.
Aktivity (5)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!