Lekce 3 - Assembler - Převod čísla na řetězec a naopak
V minulé lekci, Instalace kompilátoru assembleru ve Windows, jsme si nainstalovali kompilátor a vytvořili první zatím prázdný ASM projekt.
V dnešním tutoriálu si ukážeme, jak je možné převést vstup od uživatele (řetězec) na číslo, ale také způsob, kterým je možné převést číslo na řetězec.
Motivace
Stejně jako v jiných programovacích jazycích, i zde budeme vstup od
uživatele dostávat jako sekvenci znaků, resp. řetězec. Vstupem, resp.
vstupními daty, se rozumí např. jméno, datum narození a věk. Ve vyšším
programovacím jazyku, např. v C#, je jméno datového typu
String
, datum narození DateTime
a věk
byte
(člověk se více než 255 let určitě nedožije ). Pokud budeme opravdu chtít
pracovat s těmito datovými typy, je nutné provést konverzi. Zaměřme se
nyní pouze na věk. Pro zjednodušení řekněme, že s ním chceme pracovat
jako s Int32
. Těm z vás, kteří už se se C# setkali, určitě
došlo, že můžeme použít funkci int.Parse(...)
.
Návratovou hodnotou funkce int.Parse(...)
je onen věk, ale je
možné s ním nakládat jako s „opravdovým“ číslem.
Vraťme se nyní do světa Assembleru - světa, kde jsme odkázáni sami na sebe.
Vstupem od uživatele bude i v našem případě řetězec. Jako výstup
budeme požadovat 16bitové unsigned číslo, tedy short
(přesněji word
). Signed čísly se budeme zabývat až při
tvorbě OS, ale ti bystřejší už mají určitě tušení, jak by se to dalo
udělat.
Ve druhé části tutoriálu si popíšeme opačný proces, tedy převod čísla na řetězec. Ten můžeme uplatnit například při výpisu hodnot proměnných, výsledků výpočtu, chybového kódu atd.
Funkce string_2_ushort
; ; String 2 Unsigned Short ; ; Input: DS:SI (string pointer) ; Output: AX (number) ; string_2_ushort: pusha xor ax, ax mov bx, 0x000a .get_next_digit: mov cl, byte [ds:si] or cl, cl jz .return inc si mul bx sub cl, 0x30 add al, cl jmp .get_next_digit .return: popa ret
Algoritmus výše zařídí, že se sekvence znaků uložených v paměti
převede na číslo typu short
. Celý proces začíná
inicializací potřebných registrů - AX
, který bude obsahovat
výsledek, a BX
, v němž se nachází násobitel. Jelikož se bude
hodnota v AX
násobit, je nutné tento registr vynulovat.
Následovně se vstoupí do cyklu, uvnitř kterého se budou jednotlivé
znaky nasouvat do čísla. Znak z řetězce se nejprve nahraje do registru
CL
. Následovně se podmínkou ujistíme, že nejsme na konci
řetězce. Zvýšíme ukazatel, abychom se dostali na další znak, a obsah
registru AX
vynásobíme deseti. Tím se čísla posunout o jednu
číslici doleva. Jako poslední se v cyklu provede odečtení čísla 48,
čímž převedeme znak na číslici, a přičtení této číslice k hodnotě v
AL
. Takto dostaneme všechny znaky z řetězce do čísla.
Funkce ushort_2_string
; ; Unsigned Short 2 String ; ; Input: AX (number) ; Output: ES:DI (string pointer) ; ushort_2_string: push cs pop es mov di, .buffer pusha xor cx, cx mov bx, 0x000a .get_next_digit: xor dx, dx div bx add dl, 0x30 push dx inc cl or ax, ax jnz .get_next_digit .store_next_digit: pop ax stosb loop .store_next_digit popa ret .buffer times 0x0006 db 0x00
I v tomto algoritmu nejdříve provedeme inicializaci registrů -
ES
nastavíme na CS
. Spolu s DI
, který
bude ukazovat na proměnnou .buffer
, tvoří ES:DI
registrový pár ukazující na návratovou hodnotu funkce. CX
se
bude používat jako čítač aktuální počtu číslic. Číslo zde bude
dlouhé maximálně 5 znaků, ale kvůli instrukci loop
je nutné
provést nulování celého registru. BX
obsahuje dělitele.
Nyní vstoupíme do prvního cyklu, v němž budeme získávat a převádět
jednotlivé číslice na znaky. To provedeme celočíselným dělením, resp.
instrukcí div
. Nejprve je nutné vynulovat DX
,
jelikož dělíme 16bitovým operandem. Dělení 16bitovým operandem je zvoleno
kvůli velikosti výsledku, který by v případě 8bitového operandu vyvolal
přetečení. Po dělením deseti dostaneme jednotlivé číslice samostatně v
registru DX
jako zbytek. Ke zbytku přičteme 48, čímž dostaneme
znak příslušící danému číslu. Znak uložíme na zásobník a pak už je
pouze nutné zkontrolovat, zdali se v AX
nachází ještě nějaké
číslice.
Pokud jsou veškeré číslice na zásobníku, přejdeme k jejich
nahrávání do proměnné .buffer
. To provedeme vybráním znaku
ze zásobníku a jeho uložením na příslušnou pozici pomocí instrukce
stosb
. Instrukcí loop
zkontrolujeme, jestli jsou zde
ještě nějaké znaky na uložení do proměnné.
Po tomto cyklu už pouze obnovíme hodnoty registrů a z funkce se vrátíme.
Registrový pár ES:DI
bude ukazovat na návratovou hodnotu funkce,
tedy převedené číslo.
Jednoduché, že?
V příští lekci, Assembler - Vytvoření NASM projektu, registry a přerušení, si založíme ASM projekt a řekneme si co jsou to registry a přerušení.