9. diel - Assembler - Podmienené a nepodmienené skoky
V dnešnom ASM tutoriálu sa budeme venovať podmieneným a nepodmieneným skokom.
Skoky sa používajú pri programovaní podmienok alebo cyklov. V
kompilátora MASM môžeme pre podmienky a cykly používať príkazy
.if
, .repeat
, .while
. Kompilátor MASM za
nás automaticky vygeneruje inštrukcia skokov. Naopak v kompilátora NASM nie
sú žiadne podmienky alebo cykly a túto funkcionalitu musíme simulovať
pomocou skokov na rôzne návestí v programe a samozrejme pomocou
porovnávacích inštrukcií. Práve tejto problematike sa budeme dnes
venovať.
Skoky v ASM sú podmienené alebo nepodmienené.
Nepodmienené skoky
Nepodmienené skoky zloží k tomu, aby sme mohli neobmedzene skákať z
jednej časti kódu do druhej. V našom Hello world
programu sme takto napr. Skákali na návestie Return
.
V praxi takto môžeme potrebné vytvoriť nekonečný cyklus alebo jednoducho preskočiť nejakú časť kódu.
Inštrukcia JMP
(Jump)
Inštrukciu JMP
už tiež poznáme, slúži práve na
nepodmienečný skoku. Má iba jeden operand a tým je návestí, ktoré udáva,
kam máme skočiť.
Návestí deklarujeme pridaním :
za vybraný názov, napr.
Takto:
sem_skoc:
V strojovom kóde samozrejme nie sú žiadne návestie, ale
číselné adresy. Tie za nás vypočítava kompilátor. Pre krátke skoky
vloží do inštrukcie 8, 16 alebo 32-bitovú relatívna adresu. Tá je kladná
pre skoky smerom dopredu a záporná pre skoky späť. Pre dlhé skoky sa
použije absolútny 16 alebo 32-bitovú adresa. V strojovom kóde preto existuje
niekoľko inštrukcií skoku: E9
, EA
, EB
,
FF
. V ASM jednoducho vo všetkých prípadoch používame
JMP
a kompilátor sa podľa dĺžky skoku rozhodne, aký kód
inštrukcie použije.
Niekedy je cieľ skoku potreba určiť až za behu programu. V takýchto prípadoch sa používajú nepriame skoky, pri ktorých je adresa skoku buď v registri, alebo niekde v pamäti.
Príklad
Teraz si ukážeme prí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 predpokladá, že máme niekde deklarovanú aj funkciu
print_string
, ktorú sme si deklarovali už skôr v našom Hello
world programu.
Deklarujeme si návestí znovu:
a do registra SI
presunieme text, ktorý pomocou funkcie print_string
vypíšeme.
Ďalej skočíme na návestí znovu:
a celé sa to bude opakovať
znovu, až do nekonečna. Samozrejme nemusíme skočiť dozadu, aby sme takto
skákali stále dookola, ale môžeme skočiť aj niekam dopredu, ďalej do
programu.
Výsledkom programu teda bude:
Hello, World! Hello, World! Hello, World! Hello, World! ...
Z tohto nekonečného cyklu by sme za nejaké podmienky mohli vyskočiť preč. K tomu ale už potrebujeme skok nejako opodmínkovat.
Podmienené skoky
Častejšie budeme ale samozrejme potrebovať skočiť podmienečne. To znamená, že skočíme iba vtedy, ak bude splnená nejaká podmienka.
Inštrukcia CMP
(Compare)
Pred vykonaním samotného skoku je potreba použiť inštrukciu
CMP
(Compare). Tá má dva operandy (A
a
B
) a defakto supluje za príkaz if
z vyšších
programovacích jazykov. CMP
operandmi porovná a na výsledok
potom môžeme reagovať pomocou ďalších inštrukcií.
Celá podmienka sa zapisuje takto:
cmp [první operand], [druhý operand] j[x] [návěstí]
Poďme si ukázať tabuľku, na aké výsledky porovnania môžeme pomocou ktorých inštrukcií reagovať:
Porovnanie | A == B | A! = B | A> B | A <B | A> = B | A <= B |
---|---|---|---|---|---|---|
inštrukcie | JE (JZ) | JNE (JNZ) | JA (JNBE) | JB (JNAE) | JNB (JAE) | JNA (JBE) |
0
. Oboje je to isté, len má inštrukcie pre prehľadnosť
iný názov, aby bolo z kódu jasné, čo v danej chvíli myslíme.
Inštrukcie
JE
(Jump if Equal) a JZ
(Jump if Zero)
Skočí na dané návestie, ak sú porovnávaná čísla rovnaké
(A == B
).
Inštrukcie
JNE
(Jump if Not Equal) a JNZ
(Jump if Not Zero)
Skočí na dané návestie, ak sú porovnávaná čísla rôzne
(A != B
).
Inštrukcie
JA
(Jump if Above) a JNBE
(Jump if Not Below or
Equal)
Skočí na dané návestie, ak je prvá porovnávanú číslo
väčšie ako druhé (A > B
).
Inštrukcie
JB
(Jump if Below) a JNAE
(Jump if Not Above or
Equal)
Skočí na dané návestie, ak je prvá porovnávanú číslo
menšie ako druhé (A > B
).
Inštrukcie
JNB
(Jump if Not Below) a JAE
(Jump if Above or
Equal)
Skočí na dané návestie, ak je prvá porovnávanú číslo väčšie
alebo rovné ako druhé (A >= B
).
Inštrukcie
JNA
(Jump if Not Above) a JBE
(Jump if Below or
Equal)
Skočí na dané návestie, ak je prvá porovnávanú číslo menšie
alebo rovné ako druhé (A <= B
).
Ak viete niečo o reprezentáciu čísel v počítači, tak čísla, ktoré môžu mať aj zápornú hodnotu, sú v pamäti uložené inak (pretože musí mať vymedzenú pamäť aj pre záporné hodnoty, sú posunuté o polovicu rozsahu). Toto má vplyv na porovnávanie čísiel a ASM za nás problém nerieši. Operácie pre skoky, ktoré sme si ukázali, platí len pre unsigned čísla (teda tá, ktorá môžu byť len nezáporná). Ako sa s tým vysporiadať u signed čísel si ukážeme nabudúce.
Príklad
Určite si teraz zaslúžime príklad. Tu 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ávestie podľa toho, či je hodnota 1
> 2
.
Inštrukcie TEST
Najčastejšie sa inštrukcie TEST
používa pre zistenie, či
je hodnota v registri kladná, záporná, alebo nulová.
Porovnanie | A == 0 | A! = 0 | A <0 | A> = 0 |
---|---|---|---|---|
inštrukcie | JZ | JNZ | JS | JNS |
test ax,ax jz ax_je_nulove js ax_je_zaporne
Ak sú operandy inštrukcie TEST
rôzne, vykoná sa logická
operácia AND. Tým môžeme testovať jednotlivé bity prvého operandu.
Najčastejšie sa testuje posledný bit, čím sa zistí, či je číslo párne
alebo nepárne:
test ax,1 jz ax_je_sude jnz ax_je_liche
Cykly
U všetkých typov cyklov môžeme používať ktorejkoľvek všeobecné registre. U for cyklov ale musíme dávať pozor, aby sme nechtiac vnútri cyklu nezmenili hodnotu registra, v ktorom máme riadiacej premennú cyklu.
Ukážme si, ako pomocou skokov nahradiť rôzne druhy cyklov, ktoré určite poznáte z vyšších programovacích jazykov.
Cyklus s podmienkou na konci (do
- while
):
muj_cyklus: ; Vnitřek cyklu cmp ax,bx ja muj_cyklus
Cyklus s podmienkou na začiatku (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
V starších programoch sa tiež často používala inštrukcia
loop
. Tá robí úplne to isté ako dve inštrukcie
dec cx
a jnz návěstí
. Jediný rozdiel je v tom, že
je asi štyrikrát pomalší. Druhá nevýhoda je v tom, že ju nemožno
použiť s iným registrom než cx
.
V budúcej lekcii, Assembler - Kombinácia skokov a príznaky , sa naučíme kombinovať podmienené a nepodmienené skoky, zistíme, ako skokové inštrukcie získajú výsledok z CMP a ako porovnávať signed čísla.