Nagy
népszerűségnek örvend C-s oktatási körökben, hogy először "a világ
legegyszerűbb" programját mutatjuk meg az olvasónak, ez annyiból áll, hogy
kiírja a képernyőre: "Hello, world!", vagyis "Helló,
világ!" szöveget.
Most azonban ez a
legegyszerűbb program sem lesz túlságosan triviális, főleg azért nem, mert
megpróbálom bemutatni, hogyan lehet stílusosan megírni egy Assembly programot.
A rendes, jól
nevelt programozó (ilyenek nem léteznek, mert egy idő után megtöri őket a meló)
stílusos programokat készít, olyanokat, amit dokumentálva fejlesztett, terv
alapján, a program forrása jól kommentezett, tele van elnevezett konstansokkal,
jól tagolt, könnyen áttekinthető és megérthető, lehetőleg moduláris, hogy a
későbbiekben fel lehessen használni más programok készítéséhez. Ezek olyan
alapvető dolgok, amiket nem csak addig kellene betartani, míg a mérnök hallgató
megszerzi diplomáját...
Léteznek igazi
programozók, ezek élből nem így dolgoznak - ők mindent az utolsó pillanatban
kezdenek el, spórolnak a futási idővel és a program által kezelendő
memóriahellyel, nem pazarolják a forráskódot sem, főleg nem arra, hogy
elnevezett konstansokkal bíbelődjenek, sokkal rövidebb ideig tart magát a
számot leírni, mint egy azonosítót. Már akkor optimalizálnak, amikor még le sem
írták a program kódját, olyan szinten, hogy néha nyolc épelméjű utasításból
kettő lesz, melyeknek nem biztos, hogy közük van a problémához, de
mellékhatásukként véletlenül épp az eredményt adják...
Végül pedig
léteznek olyanok, akik igazi programozónak hiszik magukat (pedig egyszerűen
csak kuszán és csúnyán programoznak), verik a mellüket, de sokszor a
legegyszerűbb problémánál elvéreznek...
Első feladatunk
tisztázni, mire lesz szükségünk. Kell egy olyan program, ami kiírja a
képernyőre azt a szöveget, hogy "Hello, world!". Ezek szerint kell
egy sztring típusú változó, ami ezt a szöveget tárolja. De mi mutatja meg a
szöveg végét? Erre két megoldás létezik, az elsőt a Turbo Pascal környezetek
használják (de a Sun XDR protokollja is hasonlóan ábrázolja a sztringeket). A
nulladik karakter a sztring hossza, (számszerű érték), a többi a szting karakterei.
Ezen tehát egy iterációval végig lehet mászni, mivel determinisztikus a
ciklusmag lefutása. A C nyelv a szövegeket nullterminált sztringként ábrázolja,
tehát a NULL karaktert (azt a karaktert, amelynek bináris kódja zéró) használja
a sztring terminálására (megtörésére), végjelként. A C sztringet másképpen (a
múltból eredeztetve) ASCIIZ (ejtsd: eszki zé) sztringnek is hívják, mert az
ASCII karaktereket egy Zéró karakter zárja. Ezt a jelölést nem ajánlom, mert a
mai karakterkódtáblák nem csak ASCII táblák, hiszen az ASCII egy 7 bites
karakterábrázolási szabvány (ANSI ASCII: American Standard Code for Information
Interchange), a mai kódtáblák viszont minimum 8 bitesek, amelyeknek az alsó 128
karaktere az ASCII tábla, vagyis az egész kódtábla már nem kizárólag ASCII.
Következő
problémánk: hogyan jelenítjük meg a szöveget a képernyőn? Erre több megoldás is
létezik, nézzük a legegyszerűbbet. Most IBM PC-t programozunk, valós módban,
MS-DOS operációs rendszer (vagy Windows / MS-DOS parancssor) alatt. Ebben az
esetben rendelkezésünkre áll az úgynevezett BIOS (Basic Input-Output System),
amelynek az a feladata, hogy az alapvető I/O tevékenységeket támogassa. Mivel a
képernyőre írás az alapvető I/O tevékenységek közé tartozik, a BIOS ad hozzá
segítséget.
A BIOS függvényei
úgynevezett megszakításokon keresztül érhetjük el. Ezt anno azért nevezték el
megszakításnak, mert ilyenkor megszakad az eredeti program futása és egy más
(általában rendszer-) program rutin futása kezdődik. A megszakítás lefutása
után a vezérlés visszakerül a hívó programhoz, tehát folytatódik annak futása.
Eredetileg 32 BIOS megszakítás létezett (0x00..0x1F), de találhatók még nem
szabványos bővítmények, ezek általában a 0x80 fölötti címeken ülnek, és a
hardver konfigurációjától függenek.
A 0x10, vagyis
"10h" a képernyővezérlő megszakítás (interrupt). Az interruptok
mögött egy függvénykönyvtár áll, melyekből a megfelelő funkciót az AH
regiszterbe töltött értékkel választahtjuk ki. A karakter kiíró függvény
például a decimális 14-es (0xE) függvény. A kiírandó karakter kódját AL
regiszterbe kell mozgani.
A billentyűzet
megszakítása a 0x16, ahol a 0x0 funkció jelenti a billentyűlenyomásra való
várakozást. Ha leütöttek egy billentyűt, annak karakterkódja az AL regiszterben
jön vissza, a billentyű Scan kódja AH-ba kerül. A Scan kód azt jelöli, hányadik
billentyűt nyomták le, vagy engedték fel a konzolon. Az ESC billentyű
karakterkódja például 27 (0x1B), Scan kódja 0x1, az Enter karakter kódja 13,
Scan kódja 0x1C, az F1 billentyű karakterkódja 0 (mert F1, mint karakter nem
létezik - ezeket hívják funkcióbillentyűknek), Scan kódja 0x3B.
Kiírni (és akár
még beolvasni is) tudunk már. Tudjuk, hogyan lehet ábrázolni egy sztringet - mi
maradjunk a C sztringeknél (később, az MS-DOS részben megértjük, miért).
Tudjuk, hogyan kell nem determinisztikus ciklust írni, itt például elöltesztelő
ciklust szoktak használni - mert előfordulhat, hogy nulla hosszú egy sztring,
vagyis végjellel kezdődik.
Érdemes még kis
feladatokhoz is apró függvényeket irogatni, ezek megkönnyítik munkánkat. A
függvények korábban már volt szó, ha még emlékszünk, ezek olyan feladatok
elvégzésére használhatóak, amelyeket egy programban sokszor el kell végezni -
de valamilyen okból kifolyólag ciklusba szervezni nem lehet. Summa summarum, az
eljárások, függvények "véletlenszerűen" meghívható programrészletek.
Ismerjük az
alapokat a TASM szintaktikájából. Már csak azt nem tudjuk, hogyan kell mindezt
egy programmá szerveznünk. Kis programok készítésénél TASM alatt a
"tiny" modellt érdemes használni. Az utasításokat a ".code"
kulcsszó mögé kell írni, az adatokat a ".data" kulcsszó mögé. A
program végét az "end" kulcsszó jelzi.
Még egy utolsó
dolgot megemlítek: a 16 bites regisztereket érdemes használni, a jó öreg BIOS
ugyanis csak ezeket ismeri.
.model tiny ; picike program lesz
.code ; következzék az kód...
push cs ; CS, vagyis a kódszegmens címét letolni a verembe
pop ds ; kicibáljuk DS-be, vagyis beállítjuk az adatszegmenst
mov si, offset szoveg ; szoveg címe SI-be kerül
kiir: ; szövegfeldolgozó ciklus feje
mov al, [si] ; AL-be betöltjük az SI címén
; található adatot
inc si ; növeljük eggyel (inkrementáljuk) SI értékét
cmp al, 0 ; ha AL-ben 0 van, az a szöveg végét jelenti
je vege ; ugrás a program végére
call putchar ; karakter kiíró függvény meghívása
jmp kiir ; ugrás vissza, a ciklus elejére
vege:
mov ah, 4Ch ; kilépés függvénykódja MS-DOS esetén
int 21h ; MS-DOS interrupt meghívása
putchar:
mov ah, 0Eh ; karakter kiírás függvénykódja
int 10h ; BIOS video interrupt
ret ; visszatérés
.data ; innentől kezdve már csak adatok jönnek
szoveg:
db "Hello, world!"
db 0
end
Idemásolom kommentek nélkül is, akkor talán
jobban láthatóak lesznek az utasítások :)
.model tiny
.code
push cs
pop ds
mov si, offset szoveg
kiir:
mov al, [si]
inc si
cmp al, 0
je vege
call putchar
jmp kiir
vege:
mov ah, 4Ch
int 21h
putchar:
mov ah, 0Eh
int 10h
ret
.data
szoveg:
db "Hello, world!"
db 0
end
Ezennel
elérkezünk arra a pontra, hogy megtanuljuk, hogyan olvassunk be adatokat
bemeneti eszközről. A következő cikk témája a BIOS billentyűzet
függvénykönyvtára lesz.
Németh Róbert - nrobcsi@freemail.hu