NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!
NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.

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ínkami

Staženo 602x (2.68 MB)

 

Předchozí článek
Assembler - ASCII tabulka a spuštění v DOSBox
Všechny články v sekci
Základy assembleru
Přeskočit článek
(nedoporučujeme)
Assembler - Instrukce pro práci s čísly
Článek pro vás napsal Jakub Verner
Avatar
Uživatelské hodnocení:
7 hlasů
Autor se věnuje programování v x86 Assembleru.
Aktivity