Assembler (Jazyk symbolických adres)
(2. část)
Aritmetické instrukce
Programátor při své činnosti potřebuje nejen přesuny dat. V každém programu jsou nutné i výpočty a to s běžnými daty, nebo s adresami. Ty se v Assembleru provádějí jen s celými čísly. Operace s desetinnými čísly jsou zdlouhavé, i když jsou proveditelné pomocí určitých algoritmů nebo instrukcí matematického koprocesoru. Většina matematických operací se provádí s čísly v registrech nebo v paměti. Označení operandů je shodné jako při přesunech. Zároveň tyto instrukce nastavují indikátory registru F. Umožní tak větvit program. Informace o nastavovaných indikátorech najdeme v seznamu instrukcí.
Sčítání
Při tvorbě programu si musíme ujasnit, jestli chceme k hodnotě na cílovém
místě přičíst 1, nebo jiné číslo. Podle toho volíme instrukci:
-
INC cíl - k cíli přičti jedna (registr, paměť), nenastavuje CF!
-
ADD cíl, zdroj - k cíli přičti zdroj (registr - hodnota, paměť - hodnota, registr - registr, paměť - registr, registr - paměť)
-
ADC cíl, zdroj - stejně jako ADD, ale přičti i bit CF, používá se ihned za instrukcí ADD pro součet velkých čísel uložených ve dvojicích registrů
Příklady:
INC AX - přičti k registru AX hodnotu 1
INC WORD PTR [EBX + 2] - přičti k slovu na adrese určené EBX + 2 hodnotu 1
ADD AX, BX - ke slovu v registru AX přičti obsah registru BX (slovo)
ADD AH, 8 - k slabice v registru AH přičti číslo 8
ADD DX, WORD PTR [EBX] - k registru DX přičti slovo na adrese EBX
ADD BYTE PTR [ESI], 30 - k slabice na adrese ESI přičti 30
ADD promenna, 5 - k deklarované proměnné přičti 5
Pokud při těchto operacích dojde k přeplnění cíle, nastaví se
flagy CF a OF do log. 1 (kromě případu INC).
Aby při odlaďování vašich programů nedošlo ke zbytečným hádkám s překladačem, uvědomte si, že zdroj i cíl musí mít stejný počet bitů.
Otázka: V EBX:EAX je první 64-bitové číslo a EDX:ECX druhé. Jaké instrukce je sečtou s výsledkem v EBX:EAX?
Odčítání
Instrukce sloužící k odčítání jsou zápisem operandů shodné s instrukcemi pro sčítání. Proto si uvedeme jen jejich seznam:
-
DEC cíl - od cíle odečti 1 (registr, paměť) nenastavuje CF!
-
SUB cíl, zdroj - od cíle odečti zdroj (registr - hodnota, paměť - hodnota, registr - registr, paměť - registr, registr - paměť)
-
SBB cíl, zdroj - stejně jako SUB, ale odečti i bit CF, používá se ihned za instrukcí SUB pro odečtení velkých čísel uložených ve dvojicích registrů
Příklady by byly shodné se sčítáním.
Navíc jsou zde tyto specifické instrukce:
-
NEG cíl - otoč znaménko v cíli (registr, paměť)
-
CMP cíl, zdroj - odečti bez změny cíle, nastav jen registr F (registr - hodnota, paměť - hodnota, registr - registr, paměť - registr, registr - paměť)
Instrukce CMP porovnává dvě čísla odečtením. Protože ale nedojde k jejich změně, použijeme tuto instrukci před větvením programu. Za CMP totiž většinou následují instrukce skoku závislé na stavu příznaků registru F.
Příklad:
#include <stdio.h>
unsigned short a, b, s, r;
int main()
{
printf("a=");
scanf("%i", &a);
printf("b=");
scanf("%i", &b);
_asm {
mov ax, a
add ax, b
mov s, ax
mov ax, a
sub ax, b
mov r, ax
inc a
dec b
}
printf("a+b=%u\na-b=%u\na+1=%u\nb-1=%u\n", s, r, a, b);
return 0;
}
Uvedený příklad ukazuje nejjednodušší použití instrukcí ADD, SUB, INC, DEC.
Násobení
I když programátoři neradi používají (nebo spíše neradi používali,
a to hodně dávno) instrukce násobení a dělení pro jejich dlouhou
dobu provádění (na procesoru 8086, u jiných procesorů je už
rychlejší, ale stejně relativně pomalé), Assembler je samozřejmě má. I tyto operace jsou definovány jen na celých číslech. Rozlišujeme také, jestli je provádíme se znaménkem, nebo bez znaménka.
-
MUL zdroj - registr AL vynásob se zdrojem (osmibitový registr, nebo paměť) a výsledek zapiš do registru AX (osmibitové násobení).
-
MUL zdroj - registr AX vynásob se zdrojem (šestnáctibitový registr, nebo paměť) a výsledek (32 bitů) zapiš do registrového páru DX, AX za sebou (šestnáctibitové násobení).
-
MUL zdroj - registr EAX vynásob se zdrojem (32-bitový registr, nebo paměť) a výsledek (64 bitů) zapiš do registrového páru EDX, EAX za sebou (32-bitové násobení).
-
IMUL zdroj - jako MUL, ale násobení se znaménkem
-
IMUL zdroj, konstanta - jako IMUL, zdroj se nejdřív vynásobí s konstantou
-
IMUL cíl, zdroj - do cíle vlož součin zdroje a cíle (16,32-bitový registr - 16,32-bitový registr, 16,32-bitový registr - paměť)
-
IMUL cíl, zdroj, konstanta - do cíle vlož součin zdroje, konstanty a cíle (16,32-bitový registr - 16,32-bitový registr - hodnota, 16,32-bitový registr - paměť - hodnota)
POZOR! O kolikabitové násobení se jedná určuje označení místa zdroje.
Dělení
Tato operace je jednou z nejzdlouhavějších. Její provádění trvá 43
period hodin (na 8086 až 184) (sčítání trvá 2 periody). Jeho
výhodou je ale to, že je možné zjistit jak výsledek po
celočíselném dělení (celočíselný podíl), tak i zbytek po celočíselném dělení (modulo). A to všechno jen jedinou instrukcí.
-
DIV zdroj - registr AX vyděl zdrojem (osmibitový registr, nebo paměť) a podíl ulož do AL, zbytek po dělení ulož do AH (osmibitové dělení)
-
DIV zdroj - dvojslovo v registrech DX, AX vyděl zdrojem (šestnáctibitový registr, nebo paměť) a podíl ulož do AX, zbytek po dělení ulož do DX (šestnáctibitové dělení)
-
DIV zdroj - čtyřslovo v registrech EDX, EAX vyděl zdrojem (dvaatřicetibitový registr, nebo paměť) a podíl ulož do EAX, zbytek po dělení ulož do EDX (dvaatřicetibitové dělení)
-
IDIV zdroj - jako DIV ale dělení se znaménkem
Použití těchto instrukcí je podobné jako násobení. Program si
musíme ošetřit tak, aby nemohlo dojít k dělení nulou. Jestliže k
němu přesto dojde, procesor zavolá přerušení INT 0 a dojde k
ukončení programu.
Příklad:
#include <stdio.h>
unsigned short a, b, d, z;
unsigned long s;
int main()
{
printf("a=");
scanf("%i", &a);
printf("b=");
scanf("%i", &b);
_asm {
mov ax, a
mul b
push ax
mov eax, 0
mov ax, dx
mov ecx, 0
pop cx
mov edx, 0x10000
mul edx
add eax, ecx
mov s, eax
mov dx, 0
mov ax, a
div b
mov d, ax
mov z, dx
}
printf("a*b=%u\na div b=%u, a mod b=%u\n", s, d, z);
return 0;
}
Př. Napište program, který vypočítá obsah trojúhelníka podle Heronova vzorce:
P = sqrt(s*(s-a)*(s-b)*(s-c)), s = (a+b+c)/2.
Programu zadáte strany a, b, c (v jazyce C), pak se výpočet provede v
Assembleru až po závěrečnou odmocninu, kterou provedete pomocí
funkce sqrt v C (musíte vložit hlavičkový soubor math.h). Výsledek vypište (pomocí funkce printf v C).
Instrukce logických operací
Logické instrukce jsou jednou z nepostradatelných pomůcek programátorů. V
Assembleru je možné provádět všechny běžné logické operace mezi
hodnotami v registrech, v paměti nebo zadanými přímo. Chybí
zde tedy instrukce pro jednotlivé bity (od procesoru 386 jsou už i
tyto instrukce). Ty však volbou vhodných algoritmů můžeme lehce nahradit.
-
NOT zdroj - neguj všechny bity zdroje (registr, paměť)
-
AND cíl, zdroj - logický součin zdroje s cílem ulož do cíle (registr - hodnota, paměť - hodnota, registr - registr, paměť - registr, registr - paměť)
-
TEST cíl, zdroj - logický součin zdroje s cílem, ale nastav jen registr příznaků F (registr - hodnota, paměť - hodnota, registr - registr, paměť - registr, registr - paměť)
-
OR cíl, zdroj - logický součet zdroje s cílem ulož do cíle (registr - hodnota, paměť - hodnota, registr - registr, paměť - registr, registr - paměť)
-
XOR cíl, zdroj - logický vylučovací součet zdroje s cílem ulož do cíle (registr - hodnota, paměť - hodnota, registr - registr, paměť - registr, registr - paměť)
Kolikabitová operace je, určuje opět specifikace zdroje a cíle.
Instrukci TEST použijeme k nastavení příznakového registru, a tak můžeme větvit program, aniž bychom ovlivnili hodnoty zdroje a cíle.
Použití logických operací
Vymaskování slabiky nebo slova
Často potřebuje programátor nastavit některé bity do hodnoty log. 1, nebo 0. K tomu mu velmi dobře poslouží právě logické operace AND nebo OR.
Máme-li slabiku ve tvaru XXXXAXXX v registru AL a chceme, aby bity X měly hodnotu 0 a hodnota bitu A zůstala zachována, provedeme instrukci AND AL, 08h (=00001000b).
Máme-li slabiku ve tvaru XXXXAXXX v registru AL a chceme, aby bity X měly hodnotu 1 a hodnota bitu A zůstala zachována, provedeme instrukci OR AL, F7h (=11110111b).
Otázka: Máme slabiku ve tvaru XXXXAXXX v registru AL a chceme, aby bity X zůstaly zachovány a hodnota bitu A byla opačná. Jakou instrukci provedeme?
Nulování registru
Zajímavější než instrukcí MOV registr, 0 je nulování pomocí XOR
registr, registr. Efekt je stejný, doba vykonání operace je kratší
(jen na 8086, na novějších je to už stejné).
Kódování
Každý rád chrání svá data před neoprávněným přístupem kódováním. K tomu dobře slouží logická operace XOR. Postup kódování:
Provedeme-li operaci XOR s konstantou a číslem, získáme kódované číslo. Pokud s kódovaným číslem provedeme opět XOR se stejnou konstantou, získáme zpět původní číslo.
Kódovaná čísla přidáme do binárního souboru programu. Před jejich použitím je dekódujeme. Protože tato čísla mohou nést např. jméno autora (v ASCII), je jméno pro běžného uživatele po zakódování nečitelné (a tedy lehce nepřepsatelné).
Pozor! Hodnota konstanty musí být při kódování i dekódování stejná.
Tento postup můžeme libovolně pozměňovat podle úrovně našich znalostí (např. XORovat první znak s druhým, druhý s třetím, ...).
Př. Ověřte si toto kódování jednoduchým programem. Programu zadáte číslo a konstantu, ten číslo zakóduje a dekóduje (v Assembleru) a obojí vypíše.