Lekce 7 - Assembler - Datové typy a proměnné
V minulé lekci, Assembler - ASCII tabulka a spuštění v DOSBox, jsme si vysvětlili ASCII tabulku, dokončili popis Hello world! programu a spustili si jej jako .com soubor v emulátoru DOSBox.
V dnešní lekci se budeme podrobněji věnovat datovým typům v ASM a samozřejmě i proměnným.
Typový systém
Datové typy v ASM nejsou rozdělené klasicky podle toho, pro jaký typ
obsahu jsou určeny. Každý z nás zná určitě Int
,
String
nebo Boolean
z vyšších programovacích
jazyků, které ve finále zabíraly v paměti určitý počet bajtů.
Překladač také kontroloval, zda jednotlivé datové typy všude sedí. V ASM
definujeme rovnou kolik paměti se má pro určitou proměnnou vyhradit a nikdo
již nekontroluje, co do tohoto prostoru následovně ukládáme.
Definice proměnných v ASM
Ve většině programovacích jazyků lze používat lokální a globální proměnné. Lokální proměnné existují jen dočasně během vykonávání funkcí. V této lekci se naučíme používat globální proměnné. Ty mají výhodu v tom, že existují po celou dobu běhu programu a jsou odevšad přístupné. Naopak velkou nevýhodou je, že je nelze použít při paralelních výpočtech v multithreaded aplikacích.
V minulých lekcí jsme si již proměnnou vytvářeli a také určili její
datový typ. Bylo to u programu Hello World, kde proměnná hlwrld
typu DB
obsahovala text "Hello world!"
, končící
ještě znaky pro konec řádku a řetězce:
hlwrld db "Hello, World!", 0x0a, 0x0d, 0x00
Globální proměnné v ASM tedy deklarujeme stylem:
Název | Typ | Hodnota |
promenna | dd | 0xDEADBEEF |
Z toho vyplývá, že Dx
, kde x
je další
písmeno, viz níže, určuje nějaký datový typ proměnné. Pojďme si tyto
direktivy představit blíže.
Direktivy DB
, DW
,
DD
, ...
V Assembleru nalezneme těchto pět typů proměnných, ale ve skutečnosti je to takový menší podvod. Jediný rozdíl je totiž jejich velikost. V této tabulce jsou direktivy a jejich velikosti:
Direktiva | Celý název | Typ | Velikost | Rozsah celých čísel | Rozsah reálných čísel |
---|---|---|---|---|---|
DB | Define Byte | byte | 1 bajt | -128 až 255 | |
DW | Define Word | word | 2 bajty | -32768 až 65535 | |
DD | Define Doubleword | dword | 4 bajty | -2147483648 až 4294967295 | -3.4e38 až 3.4e38 |
DQ | Define Quadword | qword | 8 bajtů | -9.2e18 až 1.8e19 | -1.8e308 až 1.8e308 |
DT | Define Ten Bytes | tbyte | 10 bajtů | -1.2e4932 až 1.2e4932 |
Typ tbyte
je v překladači MASM. V překladači
NASM je místo něj typ tword
.
Nenechte se zmást slovem "Word", nejedná se o nic ve spojitosti se slovem nebo textem, ale pouze o termín určující velikost 2 bajty.
U direktiv define (to jsou všechny v tabulce uvedené) musíme proměnnou zároveň také inicializovat, nebo místo hodnoty napsat otazník.
Uložení čísla do proměnné
Číslo do proměnné v Assembleru uložíme velmi jednoduše, pouze určíme velikost v bajtech pomocí příslušné direktivy:
male_cislo DB 15 vetsi_cislo DW 23093 negativni_cislo DW -23093 velke_cislo DD 342183449 desetinne_cislo DD 1.552 desetinne_cislo2 DQ 123.456 desetinne_cislo3 DT 13.8e200
Negativní čísla se ukládají jako dvojkové doplňky. Pro vícebajtové hodnoty procesor používá malou endianitu. Pokud vám tyto pojmy nic neříkají, jsou vysvětleny v kurzu Principy fungování počítačů. Negativním číslům se budeme věnovat podrobněji v dalších článcích.
Uložení řetězce do proměnné
Jak je ale možné, že do proměnné velikosti 1 bajt můžeme uložit
libovolně dlouhý řetězec? Při použití řetězce v uvozovkách nebo
apostrofech ''
se v direktivě DB
pro každé písmeno
samozřejmě vyhradí jeden bajt. Tento kód:
hlwrld db "Hello, World!", 0x0a, 0x0d, 0x00
je vlastně jen zkrácený zápis:
hlwrld db "H", "e", "l", "l", "o", ",", " ", "W", "o", "r", "l", "d", "!", 0x0a, 0x0d, 0x00
což je zkrácený zápis z:
hlwrld db "H" db "e" db "l" db "l" db "o" db "," db " " db "W" db "o" db "r" db "l" db "d" db "!" db 0x0a db 0x0d db 0x00
Toto se vztahuje k práci s pamětí, kterou se budeme také zabývat.
Doplnění řetězce na násobky bajtů
Možná vás napadlo, co by se stalo, kdybychom proměnnou s řetězcem
definovali namísto DB
např. pomocí DW
nebo
dalšími direktivami pro větší datové typy. Fungovalo by to, ale výsledek
by vždy zabíral násobek počtu bajtů daného datového typu (např. v
případě DW
násobek dvou) a text by byl na konci doplněný
nulovými znaky, aby měl požadovanou délku. Např. text "ABCDEF"
by byl při deklaraci jako DD
:
text dd "ABCDEF"
uložený jako:
0x41 | 0x42 | 0x43 | 0x44 | 0x45 | 0x46 | 0x00 | 0x00 |
(tedy jako 8 bajtů, což jsou 2 dvojslova)
Ale co ta divná čísla? Hodnoty bajtů v paměti většinou vypisujeme jako
hexadecimální čísla, protože je tento zápis kratší. Kromě písmena
H
se čísla v šestnáctkové soustavě označují někdy také
jako 0x
. Již víme, že ASCII kód znaku 'A'
je
65
a vidíme, že první bajt má hodnotu 0x41
, kde
41
hex. je opravdu 65
dec. a tedy znak
'A'
.
Jako řetězec můžeme uložit např. i znak 'y'
/
'n'
pro reprezentaci volby uživatele:
znovu DB 'y'
Neinicializovaná data
Neinicializovaná data jsou ve zkratce data, která nemají žádnou počáteční hodnotu. Taková data nabývají nějaké hodnoty až během běhu programu, např. vstupní hodnota od uživatele.
Když potřebujeme vyhradit v paměti určitý počet bajtů, například pro
delší text nebo číselné pole, použijeme operátor DUP
:
buffer DB 1000 dup(?)
V NASM se používá pseudoinstrukce RESx
:
buffer resb 1000
Pseudoinstrukce RESx
se liší podle datového typu (např.
RESB
, RESW
a RESD
).
Ale můžeme se setkat také s operátorem TIMES
, který vyplní
úsek paměti námi vybranou hodnotou:
buffer times 1000 DB 0
Proměnné a registry
Teď již víme, jak proměnné deklarovat a jaké jsou velikosti jednotlivých typů. Nyní si ukážeme, jak se dají proměnné kombinovat s registry a mezi sebou.
Stejně jako u registru je důležité dodržovat velikost. Nejdříve si ukážeme příklady pro kombinaci s registry pro překladač NASM.
value1 db 0x00 ; Definujeme proměnnou value1. value2 dw 0x00 ; Definujeme proměnnou value2. value3 dd 0x00 ; Definujeme proměnnou value3. value4 dq 0x00 ; Definujeme proměnnou value4. ; s 8bitovým registrem mov byte [value1], al ; Do proměnné value1 přesuneme hodnotu z registru AL. mov al, byte [value1] ; Do registru AL přesuneme hodnotu z proměnné value1. ; s 16bitovým registrem mov word [value2], ax ; Do proměnné value2 přesuneme hodnotu z registru AX. mov ax, word [value2] ; Do registru AX přesuneme hodnotu z proměnné value2. ; s 32bitovým registrem mov dword [value3], eax ; Do proměnné value3 přesuneme hodnotu z registru EAX. mov eax, dword [value3] ; Do registru EAX přesuneme hodnotu z proměnné value3. ; s 64bitovým registrem mov qword [value4], rax ; Do proměnné value4 přesuneme hodnotu z registru RAX. mov rax, qword [value4] ; Do registru RAX přesuneme hodnotu z proměnné value4.
V překladači MASM je syntaxe trochu jednodušší:
value1 db 0x00 ; Definujeme proměnnou value1. value2 dw 0x00 ; Definujeme proměnnou value2. value3 dd 0x00 ; Definujeme proměnnou value3. value4 dq 0x00 ; Definujeme proměnnou value4. ; s 8bitovým registrem mov value1, al ; Do proměnné value1 přesuneme hodnotu z registru AL. mov al, value1 ; Do registru AL přesuneme hodnotu z proměnné value1. ; s 16bitovým registrem mov value2, ax ; Do proměnné value2 přesuneme hodnotu z registru AX. mov ax, value2 ; Do registru AX přesuneme hodnotu z proměnné value2. ; s 32bitovým registrem mov value3, eax ; Do proměnné value3 přesuneme hodnotu z registru EAX. mov eax, value3 ; Do registru EAX přesuneme hodnotu z proměnné value3. ; s 64bitovým registrem mov value4, rax ; Do proměnné value4 přesuneme hodnotu z registru RAX. mov rax, value4 ; Do registru RAX přesuneme hodnotu z proměnné value4.
Závorky
V NASM se můžeme setkat s proměnnou v závorkách, ale i s proměnnou, která v závorkách nebude:
text db "Ahoj, Karle.", 0x00 ; Definujeme proměnnou text. cislo dw 0xC0DE ; Definujeme proměnnou cislo. mov si, text ; Do registru SI přesuneme adresu proměnné text. mov ax, word [cislo] ; Do registru AX přesuneme hodnotu proměnné cislo.
Závorky používáme pouze tehdy, pokud chceme přesunout hodnotu.
Pokud potřebujeme adresu proměnné, například kvůli vypisování textu, závorky nepoužijeme.
Přiřazení z proměnné do proměnné
Pokud chceme přenést hodnotu z jedné proměnné do druhé, musíme použít registr:
; Přenesení hodnoty z jedné proměnné do druhé (v překladači NASM) mov al, byte [value1] ; Do registru AL přesuneme hodnotu z proměnné value1 mov byte [value2], al ; Do proměnné value2 přesuneme hodnotu z registru AL value1 db 0x00 ; Definujeme proměnnou value1. value2 db 0x00 ; Definujeme proměnnou value2.
Takto bychom pracovali s číselnými hodnotami, ale jak se to dělá s textem?
Ukazovací registry
Pokud chceme pracovat s textem, je dobré naučit se pracovat s ukazovacími
registry. Ty spadají pod univerzální registry a patří sem registry
DI
a SI
. Pro "čtení" textu se používá
SI
a pro nějaké úpravy DI
. Oba dva registry jsou
16bitové. Již jsme se s nimi setkali v lekci o
programu Hello world.
Registr SI
(Source Index)
Registr SI
je ukazatel na zdrojová data při kopírovacích
operacích.
Registr SI
tvoří pár s registrem DS
.
Registr DI
(Destination
Index)
Registr DI
je ukazatel na cílová data při kopírovacích
operacích.
Registr DI
tvoří pár s registrem ES
.
Zde je kód k programu nazvanému Písmenka. Program "analyzuje" větu v
proměnné veta
a vybere z ní písmenka 'a'
a
'A'
, která přesune do proměnné pismenka
. Pracuje
se zde jak s registrem DI
, tak s registrem SI
. Kód
naleznete v příloze.
org 0x0100 mov di, pismenka ; Nastavíme adresu v registru DI na proměnnou pismenka. mov si, veta ; Nastavíme adresu v registru SI na proměnnou veta. nacti_znak: lodsb ; Načteme znak z proměnné veta do registru AL. cmp al, 'a' ; Pokud hodnota odpovídá znaku 'a' (pouze malému)... jz .pridej_znak ; ...skoč na .pridej_znak cmp al, 'A' ; Varianta pro 'A'... jz .pridej_znak ; ...skoč na .pridej_znak cmp al, 0x00 ; Pokud je číslo znaku 0x00 (NULL)... jz .prokracuj ; ...skoč na .pokracuj. jmp nacti_znak ; Pokud se jedná o jinou hodnotu, pokračujeme v cyklu. .pridej_znak: mov byte [di], al ; Přesuneme hodnotu na adresu v registru DI (do proměnné pismenka). inc di ; Posuneme se na další adresu (nastavíme ukazatel na další bajt v proměnné pismenka). jmp nacti_znak .pokracuj: mov byte [di], 0x00 ; Nyní zakončíme řetězec přesunutím hodnoty 0x00 do proměnné pismenka. mov si, pismenka ; Nastavíme adresu v registru SI na proměnnou pismenka. vypis_znak: lodsb ; Načteme znak z proměnné pismenka do registru AL. cmp al, 0x00 ; Pokud je číslo znaku 0x00 (NULL)... jz .konec ; ...skoč na .konec. mov ah, 0x0e ; Funkce BIOSu pro tisk znaku. int 0x10 ; A nyní, BIOSe, vytiskni znak! jmp vypis_znak ; Pokračujeme v cyklu. .konec: ret ; Vrať kontrolu operačnímu systému. veta db "Ahoj, jak se mas, Karle?", 0x00 ; Definujeme proměnnou veta. pismenka resw 0x05 ; Definujeme proměnnou pismenka o 10 bajtech.
Můžete si všimnout, jak se s takovými ukazovacími registry pracuje. Tady je jednodušší příklad:
mov di, text ; Nastavíme adresu v registru DI na proměnnou text. mov byte [di], 0x41 ; Na adresu přesuneme hodnotu 0x41 ('A'). inc di ; Posuneme ukazatel. mov byte [di], 0x42 ; Na adresu přesuneme hodnotu 0x42 ('B'). inc di ; Posuneme ukazatel. mov byte [di], 0x43 ; Na adresu přesuneme hodnotu 0x43 ('C'). inc di ; Posuneme ukazatel. mov byte [di], 0x00 ; Na adresu přesuneme hodnotu 0x00 (NULL). mov si, text ; Nyní nastavíme registr SI na adresu proměnné text. vypis_znak: lodsb ; Načteme hodnotu z adresy do registru AL a posuneme ukazatel. cmp al, 0x00 ; Pokud je hodnota v registru AL rovna hodnotě 0x00 (NULL)... jz .konec ; ...skočíme na .konec. mov ah, 0x0e ; Funkce BIOSu pro tisk znaku. int 0x10 ; A nyní, BIOSe, vytiskni znak! jmp vypis_znak ; Pokračujeme v cyklu. .konec: ret text resd 0x01 ; Definujeme proměnnou text o 4 bajtech.
Instrukce INC
(Increment)
Instrukce INC
má jeden parametr a tím může být registr nebo
proměnná. Z anglického increment vyplývá, že instrukce má za úkol
"přičíst číslo jedna" buď do registru, nebo do proměnné.
V příští lekci, Assembler - Instrukce pro práci s čísly, si probereme instrukce pro práci s čísly.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkamiStaženo 612x (2.68 MB)