2. diel - Assembler - Prevod čísla na reťazec a naopak
V minulej lekcii, Assembler - Zásobník , sme si uviedli zásobník, ako sa dá používať, a na čo si dať pozor, keď s ním pracujeme.
V dnešnom ASM tutoriálu si predvedieme, ako previesť binárne číslo na reťazec pomocou primitívneho algoritmu a rozoberieme si, ako funguje. Vyskúšame si tým skoky v praxi.
Motivácia
Niekedy potrebujeme previesť číslo na reťazec alebo opačne. Rozdiel medzi číslom a reťazcom z ohľadu uloženia v pamäti by nám mal byť jasný. Číslo je jedno číslo, ktoré môžeme napr. Pripočítať k inému číslu. Reťazec je oproti tomu uložený ako sekvencie znakov, teda niekoľko za sebou idúcich čísel, každé označujúci kód nejakého znaku.
Funkcia int_to_string
Začneme prevodom z čísla na reťazec. Najprv sa pozrieme na samotný kód, ktorý si vzápätí vysvetlíme:
int_to_string: pusha xor cx, cx mov bx, 0x000a mov di, .buffer .push: xor dx, dx div bx inc cx push dx test ax, ax jnz .push .pop: pop dx add dl, 0x30 mov byte [di], dl inc di dec cx jnz .pop mov byte [di], 0x00 popa mov ax, .buffer ret .buffer times 7 db 0x00
Kód sa môže zdať na prvý pohľad pomerne zložitý, ale hneď sa
presvedčíme o tom, že je naozaj jednoduchý. Majme napríklad číslo
158
. Rozhodneme sa, že si toto číslo prevedieme na reťazec.
Vložíme ho teda do nášho algoritmu a ten začne pracovať.
Najprv sa uloží hodnoty všetkých registrov pomocou inštrukcie
PUSHA
a pripraví sa na potrebné hodnoty tie registre, ktoré
budeme používať. To sú:
- Register
CX
sa bude používať ako počítadlo, ktoré nám v druhom cykle povie, z koľkých znakov sa číslo skladá. - Register
BX
sa bude používať pre delenie10
, tak získame jednotlivé čísla, z ktorých sa bude reťazec skladať. - A do registra
DI
uložíme adresu, kde sa nachádza nášbuffer
(do neho sa bude reťazec ukladať).
Teraz sa nachádzame v prvom cykle. Tu sa číslo 158
vydelí
10
. Do registra DX
sa uloží zvyšok po tomto
delení, teda číslo 8
. Všetky čísla postupne ukladáme na
zásobník a pokračujeme, dokiaľ nie je zvyšok po delení 0
. To
zaisťuje inštrukcie TEST
. Pri každom vykonaní cyklu sa hodnota
v registri CX
zvýši o 1
.
Prechádzame do druhého cyklu. Tu začneme všetky čísla zo zásobníka
postupne vyberať. Ku každému číslu prirátame hodnotu 0x30
,
čo je 48
. Dostaneme sa tak k znakom čísel v ASCII tabuľke.
Každý, teraz už znak, presunieme do buffer
. Presúvanie
prebieha, dokiaľ nie je v registri CX
hodnota 0
, s
každým cyklom sa hodnota v ňom zníži o 1
.
Po presunutí všetkých čísel obnovíme hodnoty všetkých registrov
pomocou inštrukcie POPA
a reťazec presunieme do registra
AX
.
Funkcia string_to_int
Teraz si ukážeme, ako to urobiť obrátene. Niekedy totiž potrebujeme previesť reťazec na číslo, napríklad užívateľský vstup. Opäť si najprv ukážeme kód:
string_to_int: pusha mov si, ax xor ax, ax mov bx, 0x000a .next: push ax lodsb mov cl, al pop ax cmp cl, 0x00 jz .return sub cl, 0x30 and cx, 0x00ff mul bx add ax, cx jmp .next .return: mov [.buffer], ax popa mov ax, [.buffer] ret .buffer dw 0x0000
Najprv si opäť ukladáme hodnoty všetkých registrov pomocou inštrukcie
PUSHA
a nastavujeme si potrebné registre:
- Do registra
SI
presunieme adresu vstupného reťazca z registraAX
. AX
vynulujeme. Budeme doň ukladať výstup.- Register
BX
nastavíme na hodnotu0x000a
(10
), budeme ním násobiť.
Prejdeme do cyklu .next
. Najprv si uložíme hodnotu registra
AX
na zásobník a pomocou inštrukcie LODSB
načítame do AL
prvý znak zo vstupného reťazca. Ten presunieme
do registra CL
a AX
obnovíme. Ak je v registri
CL
hodnota 0x00
(0
), znamená to, že sme
na konci reťazca a skočíme na návestí .return
. V opačnom
prípade odpočítame z registra CL
hodnotu 0x30
(48
), aby sme sa dostali k číslam v ASCII tabuľke.
Pretože by mohol register CH
niečo obsahovať, vykonáme
logický súčin s hodnotou 0x00ff
(255
). Mohli by sme
použiť aj XOR CH, CH
. Register AX
vynásobíme
0x000a
(10
) a pripočítame k nemu hodnotu z registra
CX
.
Aby sme si vysvetlili, prečo násobíme AX
hodnotou
0x000a
(10
), prejdeme si cyklus ešte raz. Opäť
použijeme číslo 158
. V AX
máme prvú číslicu,
1
. Teraz skočíme na návestí .next
a načítame
ďalší znak z reťazca do CL
. Hodnota nie je 0x00
(0
), pokračujeme ďalej. Z CX
vytiahneme hodnotu
5
a prechádzame k násobenie. AX
sa teda vynásobí
0x000a
(10
), teraz je v ňom hodnota 10
.
K tej sa pripočíta hodnota 5
. To isté urobíme aj s hodnotou
8
. AX
sa opäť vynásobí, bude obsahovať hodnotu
150
ak tej pripočítame hodnotu 8
. Násobenie nám
teda zabezpečuje, že jednotlivé cifry budú na svojich miestach.
Teraz sme na konci reťazca, skáčeme teda na návestí
.return
, do premennej .buffer
presunieme hodnotu
registra AX
(158
), obnovíme hodnoty všetkých
registrov inštrukcií POPA
a do AX
presunieme hodnotu
z premennej .buffer
.
Jednoduché, že?
V budúcej lekcii, Assembler - Bitové operácie , sa budeme venovať inštrukciám pracujúcim s bity.