Tady není nutný žádný velký úvod, jdeme počítat a ukládat si čísla do zásoby.
Jednoduchá věc. Instrukce sečte argumenty <kam>+<co> a výsledek uloží do argumentu <kam>. Z toho plyne, že <co> může být registr, konstanta nebo místo v paměti, ale <kam> nemůže být konstanta. Zároveň platí, že jako u všech ostatních instrukcí nemůžou být oba parametry odkazem do paměti. Pokud je výsledek sčítání větší než je možno uložit do výsledku, neboli potřeboval by se ještě jeden bit, nastaví se vlajka Carry na 1. To se využívá v následující instrukci.
Provede tuto operaci: <kam> := <kam> + <co> + CF. Většinou se provádí ihned po instrukci ADD, pokud potřebuji např. sečíst dvě 64-bitová čísla(nebo 32-bitová na procesoru 286). To provedu tímto způsobem:
; Dejme tomu, že EBX:EAX je první 64-bitové číslo a EDX:ECX druhé. ADD EAX,ECX ADC EBX,EDX
Jednoduché-přidá k argumentu <co> jedničku. Ptáš se, proč tato instrukce vůbec existuje, když lze použít ADD <co>,1 ? Tato instrukce je mnohem rychlejší než ADD, i když se tento rozdíl pomalu smazává vývojem procesorů.
To samé jako ADD, ale odčítání.
To samé jako ADC, ale odčítání.
To samé jako INC, ale odčítání.
Instrukce násobení. MUL násobí čísla bez znaménka, IMUL je znaménkově rozšířené. Funguje takto : AX := AL*<co>, pokud je <co> 8-bitové, jinak DX:AX := AX*<co>.
Instrukce dělení. DIV dělí čísla bez znaménka, IDIV je znaménkově rozšířené. Pokud je <co> 8-bitové, funguje takto : AL := AX div <co> a zároveň vyleze i zbytek AH := AX mod <co>. U 16-bitového <co> je to obdobně : AX := DX:AX div <co> a DX := DX:AX mod <co>.
Instrukce provede následující operaci- <kam> := <kam> AND <co>.
Mimo jiné se používá i k nulování vybraných bitů. Neboli - pokud chci vynulovat třeba třetí(počítáno od nuly) bit registru AL, provedu jednoduše AND AL,NOT 8. Proč zrovna číslem NOT 8(= 247D = 11110111B) ? Stačí si uvědomit, jak funguje AND-výsledek operace(mezi dvěma bity) je jednička pouze tehdy, pokud oba zdrojové bity jsou jedna. Číslo NOT 8 tedy vynuluje 3. bit a ostatní nechá beze změny.
Instrukce provede následující operaci- <kam> := <kam> OR <co>.
Tady uvedu také jedno z mnoha využití. Pokud chci nějaký bit nastavit, použiji právě OR. Chci nastavit 3. bit registru AL, tak udělám OR AL,3. Protože výsledek operace je jednička, pokud alespoň jeden ze zdrojových bitů je jedna, nastaví se třetí bit a ostatní zůstanou nezměněny.
Instrukce provede následující operaci- <kam> := <kam> XOR <co>. Zde vysvětlím jednu maličkost. Pokud xornu dvě stejná čísla, vyjde vždy nula - viz. definice operátoru XOR. K čemu to ale je? Pokud chci do AX dát nulu a potřebuju to udělat rychle, použiji XOR AX,AX, protože je to rychlejší než MOV AX,0. Na Pentiu je to už asi stejný(tam stejně veškerá optimalizace tohoto typu padá), ale na starších strojích je to rychlejší.
A má ještě jednu důležitou funkci-uplatňuje se při volání procedur(chceš-li tak funkcí) a do něj se také umísťují lokální proměnné a předávají se parametry funkcí předávané hodnotou(pokud jsou předávané odkazem-v Pascalu je před nimi VAR, je na ně v zásobníku pointer). Při volání funkce se nejprve do zásobníku uloží parametry v tom pořadí, v jakém jsou napsány v deklaraci a pak se pomocí instrukce CALL zavolá funkce. Instrukce CALL uloží do zásobníku adresu následující instrukce(adresa CS:IP) a skočí na funkci. Funkce si do registru BP uloží hodnotu SP, vyhradí si místo na lokální proměnné(které mají po překladu adresy typu SS:[BP+něco]). Po skončení funkce se provede MOV SP,BP a pak instrukce RET x.RET přečte ze zásobníku adresu uloženou instrukcí CALL, tak skočí a pak přičte k registru SP hodnotu x(která se v tomto případě rovná velikosti parametrů funkce-nechci přece, aby mi v zás. zůstaly a při každém volání funkce se jeho kapacita snižovala).
Tahle instrukce provede uložení 16 nebo 32-bitové hodnoty do zásobníku. Několik instrukcí push lze psát rovnou jako PUSH AX BX CX. Ve skutečnosti provede :
SUB SP,velikost_argumentu_<co> MOV SS:[SP],<co>
Tahle instrukce načte ze zásobníku 16 nebo 32-bitovou hodnotu. Lze též jako u PUSH použít zkrácený zápis. Ve skutečnosti provede :
MOV <kam>,SS:[SP] ADD SP,velikost_argumentu_<kam>
Tato instrukce provede současné uložení těchto registrů : AX, BX, CX, DX, SI, DI, BP, SP. POZOR-není to pouze vymoženost překladače(jako je PUSH AX DI BP, ale je to opravdová instrukce procesoru).
Instrukce zavolá návěští, s tím, že nejdřív uloží adresu následující instrukce, aby se pak program mohl vrátit zpět. Ve skutečnosti provede :
PUSH CS PUSH IP JMP <návěští>Mimochodem-možná jsi si všiml jisté podobnosti instrukcí INT a CALL. Vlastně jediný rozdíl je ten, že INT navíc PUSHne do zásobníku Flagy. Význam je jasný-protože se INT může vyvolávat hardwarově, neboli v jakémkoliv možném(nečekaném) okamžiku, musí se prostě uložit i FLAGy, které se téměř po každé instrukci mění a tudíž by je obsluha přerušení nebyla schopná zachovat. O zachování ostatních registrů se už ovšem postarat musí.
Návrat z poslední funkce=skok za poslední CALL. Pokud je přítomen parametr <hodnota>, je po skoku ještě provedeno SP := SP + <hodnota>. Provede se :
POP IP POP CS ADD SP,<hodnota> ; Pokud existuje parametr <hodnota>Stejně jako u instrukce CALL je i instrukce RET podobná instrukci IRET. Důvod je zřejmý-stejně jako RET je jednoduše řečeno opak CALLu, IRET je opak INTu. Proto se v instrukci IRET musí ze zásobníku vyndat i FLAGy.
Udělej program, který na obrazovku nakreslí spirálu. Začne se vlevo nahoře a budou se kreslit třeba hvězdičky postupně podél okraje obrazovky nejdřív doprava, pak dolů, doleva, nahoru, doprava ... Až do zaplnění obrazovky. Pro zájemce-na začátku můžeš uložit původní obsah obrazovky třeba do zásobníku(na PUSHovat tam celoou videoram, budou to 4 Kb) a po skončení a odklepnutí klávesy zase původní obraz obnovit.
K tomuto programu vysvětlím další užitečnou věc-uspořádání videopaměti v textovém módu. Ve většině textových módů je videoram uložená v segmentu číslo 0B800H. Paměť je uspořádaná po řádkách(řádka má v normálním módu 80 znaků) a pro každý znak na obrazovce tam jsou dva byty-hodnotu znaku a barvu. To znamená, že znak vlevo nahoře je uložen na adrese 0B800H:0 a jeho barva na 0B800H:1. Ještě pro přesné pochopení uvedu třeba znak 3. řádce a 2. sloupci. Jeho adresa je tedy 0B800H:160*2+2 a barva je na 0B800H:160*2+3. Tolik tedy k textovému módu, na grafiku se podíváme později(možná).
Napiš program, který si nechá zadat číslo v šestnáctkové soustavě a vypíše ho desítkově. To není nic jednoduchého. Předpokládám, že víš, jak se obecně převádí mezi číselnými soustavami a proto ti pouze ukážu způsob, jak se dá velice jednoduše převádět mezi dvojkovou(počítačovou) a šectnáctkovou soustavou:
Kolik je vlastně číslo 16 ve dvojkové s. - je to 10000. Neboli rozsah šestnáckových číslic 0-F je dvojkově 0000-1111. To znamená, že poslední cifra šestnáctkového čísla se do dvojkového přenáší jako jeho poslední čtyři cifry. Obdobně to platí i u dalších cifer. druhá cifra šest. čísla je jako druhé čtyři cifry dvojkového. Proto nemusíš nějak obecně násobit mocninou čísla soustavy atd. Prostě vezmeš čtveřici bitů a převedeš ji pomocí tabulky šest. cifer na šect. číslo.
Ve tvém programu ovšem potřebuješ nejen převést šestnáctkové číslo na dvojkové, ale poté ho musíš ještě předělat na desítkové. Tam už se ale bez dělení neobejdeš.
Tento výtvor by měl vypsat na obrazovku jakýsi trojúhelník. Na první řádku napíše jeden znak #219 = plný obdélník(nedonutil jsem windows, aby ho nějak stvořily). Na druhou řádku napíše dva, a tak dále až do 80. Pak to zase začne klesat. Zabere to 160 řádek, takže to celé pěkně pojede nahoru.
Máš mnoho možností, jak to udělat. Buď pro každou řádku volat službu vypiš znak tolikrát, kolikrát je třeba nebo si můžeš nadefinovat řetězec dlouhý 81 znaků a obsahující samé znaky #219 a na konci znak "$" a měnit počáteční offsetu výpisu, čímž budeš vypisovat(službou 9) požadovaný počet znaků. Dokonce nemusíš vůbec používat služby na vypisování. Můžeš to dávat přímo do videoram, ale musel bys jí i scrollovat(posunovat) a to je podle mě zbytečně složité. Já jsem pro variantu č. 2.
Vzorová řešení |
Úvodní stránka | Registry procesoru | Posuny a rotace |
Stránku připravuje Lukáš Valenta,
1. v celostátním kole soutěže v programování, kategorie mládež.