Tömbök

 

A tömb azonos típusú elemekből álló, sorosan tárolt adatcsoport. Amennyiben hossza előre ismert, statikus tömbnek nevezik, ha hossza a program futása során (run time) változik, dinamikus tömbnek, vagy listának nevezzük. Ebben a cikksorozatban a statikus tömbökkel foglalkozunk. A tömböknek több dimenziója is lehet, ez alapján tudunk belőlük kijelölni egy adott elemet. Ezt a kijelölést hívjuk indexelésnek, a jelölő dimenziókat pedig - mi másnak, mint - indexnek.

Mikor lehet szükségünk tömbök alkalmazására? Amennyiben sok, azonos jellegű adatot kell tárolni. Például írjunk olyan programot, amelyik megmondja, hogy az év megadott hónapja hány napos. Fel kell venni egy 12 elemű egydimenziós tömböt, amelyet elemenként az adott hónap napjainak számával töltünk fel, majd írunk egy programot, amiben beolvasunk egy számot, és visszakeressük a hozzá tartozó hónap napjainak számát a tömbből.

 

A logikai terv már adott, vegyük a fizikai tervet. Figyelembe kell venni a tömb elemeinek méretét, hogy a lehető legkisebb memóriát foglalja le a program. A hónapok napjainak értéke, mint szám az [1,31] tartományba esik, ezt 5 biten ábrázolhatjuk, mert 2^5 = 16, ebbe belefér az adott tartomány. Számokat kiolvasni azonban csak 8, 16 és 32 bitenként tud a processzor, tehát vegyük az 5 bithez legközelebb álló, 8 bites ábrázolást. 8 bites elemekből álló, vagyis úgynevezett "bájtos tömböt" fogunk deklarálni.

Korábban már említettem, hogy az EBX regisztert érdemes címzéseknél használni, a tömb indexelés pedig teljes mértékben fedi a címzés fogalmát. EBX regisztert a hónap, mint szám értékével töltjük majd fel, és ezzel indexelünk. Valamire azonban az indexelésnél figyelni kell: az első hónap valójában a nulladik hónap, mert a tömb nulladik eleme adja az első hónap számát. EBX értékét tehát eggyel csökkenteni kell. A számítógépek és a matematikusok már csak ilyenek...

Azt hiszem, minden tisztázott, lássuk a medvét (tekintsük az macit)...

 

mov ebx, 2 ; februar

mov esi, Offset tomb

dec ebx

mov al, ds:[esi+ebx] ; AL-be kerul a napok szama

vege:

 

; ...

; ide kerul a program kilepteteset

; elvegzo kodreszlet

 

tomb:

db 31 ; januar

db 28 ; februar

db 31 ; marcius

db 30 ; aprilis

db 31 ; majus

db 30 ; junius

db 31 ; julius

db 31 ; augusztus

db 30 ; szeptember

db 31 ; oktober

db 30 ; november

db 31 ; december

 

Tudom, tudom, a február lehet 29 napos is. Na tessék, itt van egy olyan program, ami erre is figyel. EAX-ben azt kell megadni, hogy szökőév van-e. Ha igen, akkor amennyiben a 2. hónap napjait kérdezzük, a tömb februárhoz tartozó elemének értékéhez egyet hozzáad.

 

mov ebx, 2 ; februar kell mar megint... ;)

; csak nem birunk rola leszokni?!

 

mov esi, Offset tomb

mov ah, 0 ; ez majd kesobb kell

dec ebx

cmp ebx, 1

je SzokoevEllenorzes

jmp Kiolvas ; ha nem februar

SzokoevEllenorzes:

cmp eax, 1

je DeJoSzokoev

jmp Kiolvas ; ha nem szokoev

DeJoSzokoev:

mov ah, 1 ; szokovben februar eggyel tobb (masnapos:)))

Kiolvas:

mov al, ds:[esi+ebx]

add al, ah ; hozzaadjuk es kesz

vege:

 

; ...

 

tomb:

db 31 ; januar

db 28 ; februar

db 31 ; marcius

db 30 ; aprilis

db 31 ; majus

db 30 ; junius

db 31 ; julius

db 31 ; augusztus

db 30 ; szeptember

db 31 ; oktober

db 30 ; november

db 31 ; december

 

Nézzünk példát többdimenziós tömbökre is. Vegyünk olyan esetet, amikor egy mátrixban számokat tárolunk, mert egy koordináta rendszer pontjait ábrázoljuk vele. Ha egy adott koordinátán az érték 0, ott nincs jelölt pont, ha 1, akkor jelölt pont van. Hasonlóan tárolják a színes képeket is. Ezt bittérképnek (bitmap) hívják, innen a Windows képformátumának "BMP" (BitMaP) kiterjesztése.

Egy (3 x 3)-as mátrixot fogunk kezelni, s ezzel megmutatom, hogy a többdimenziós tömbök elemei is természetesen sorfolytonosan foglalják a memóriát. Az elemek indexe a [0,2] tartományba esik, mert az első elem a nulladik, vagyis a harmadik a második (juuuuuuuj)... A sorfolytonos elhelyezkedés miatt a 0. sor 2. eleme után az 1. sor 0. eleme következik, etc, estöbö, estöbö.

 

Hogy meghatározzuk az ehhez tartozó relatív memóriacímet, vagyis az eltolást a tömb elejéhez képest, a koordináta Y értékét meg kell szorozni 3-mal (mert ennyi elemet kell soronként átugrani), majd ehhez hozzá kívántatik adni az X koordináta értékét.

A szorzást az Intel processzoron a MUL (multiplicate) utasítással érhetjük el, 8 biten AL regiszterbe kerül a szorzandó, BL-be a szorzó, az eredmény pedig AX-ben jön vissza, 16 biten, mert ha pl. 60-at szorzunk össze 60-nal, az 3600, ez pedig csak 16 biten fér el. Gondoskodni kell tehát a régi értékek mentéséről.

 

Adjuk meg a mátrixot:

 

( 0, 0, 1 )

( 0, 1, 1 )

( 1, 0, 1 )

 

A következő program a 2. sor 1. elemét fogja kiolvasni (a gép "agyával" gondolkodva az 1. sor 0. elemét). Nézzük meg a mátrixot, itt egy 0 értékű elem áll.

 

mov ah, 1 ; Y

mov al, 0 ; X

 

push ax ; mentes a szorzas miatt

 

mov al, ah

mov bl, 3

mul bl

mov bx, ax

 

pop ax ; visszatolt

 

mov ah, 0 ; felso resz nullazasa

add bx, ax ; 16 bites osszeadas

 

mov si, Offset matrix

mov al, ds:[si+bx] ; AL-be kerul ami kerul ))

vege:

 

matrix:

db 0 ; (0,0)

db 0 ; (0,1)

db 1 ; (0,2)

db 0 ; (1,0)

db 1 ; (1,1)

db 1 ; (1,2)

db 1 ; (2,0)

db 0 ; (2,1)

db 1 ; (2,2)

 

 

Kövessük végig a mátrixot elemenként, sorfolytonosan! A (2,1) koordonáta, ami gépi indexelés szerint (1,0) a 3-as relatív címen van, mert index=( Y * elemek_száma + X), vagyis ((1*3=3)+0)=3. A 3. indexű elem értéke 0, ez valóban a helyes eredmény.

 

Konstans- és makródefiníciók

 

Miért van szükség konstansokra? Mint említettem, egy konstans literál olyan érték, melyet elnevezve is el tudunk érni, vagyis azonosítója van. Ilyen volt az ESO, NAP, SZEL, es persze a NAPSZEMUVEG is. Hogyan definiáljunk konstans literálokat? Az "equ" (equal, vagyis "egyenlő") kulcsszóval...

 

ESO equ 1

HO equ 2

SZEL equ 3

NAP equ 4

UDVOZLES equ "Sziamia!"

 

Ezek után ha valahova beírjuk, hogy SZEL, akkor a fordító behelyettesíti oda a 3 számot. Ha valahova beírjuk, hogy UDVOZLES, oda a fordító beírja, hogy "Sziamia!". Természetesen lehetne a konstansok azonosítóját kisbetűvel is írni, ez a nagybetűsség programozási stílus kérdése, ajánlott ezt így végezni, jobban olvasható a program, elsőre látszik, hogy egy nagybetűs azonosító mögött egy konstans rejtőzik.

 

Mi a makró? A makró egy elnevezett utasításblokk (vagyis programrészlet), azonosítóval hivatkozhatunk rá, ekkor a mögötte álló forráskódot a fordító behelyettesíti a hivatkozás helyére. A makró tehát arra jó, hogy hosszabb utasítássorozatokat ne kelljen sokszor újra és újra leírni. A makrók neve ugyanúgy, mint az utasításoké és függvényeké, kisbetűvel írandó - ez persze csak stílusossági szempontjából érdekes.

 

Esetenként a makrókat egymásba lehet ágyazni, de ez fordítóprogram kérdése (vagyis akkor lehet, ha a fordító megengedi).

Nézzünk egy példát makróra:

 

kilepes MACRO

mov ax, 4C00h

int 21h

ENDM

 

Elfajult programozók képesek tömbelemeket is makrókba zúzni, ez az igazi-programozó-utánzás azonban nem vezet mindig jóra, mert nehezítheti a program megértését (mikor két év múlva elő kell venni valamit és akkor előjön, hogy mit-hol-miért-mivan?).

 

matrix_2x2 MACRO

db 0

db 0

db 0

db 0

ENDM

 

 

Paraméterek

 

A paraméterek egy adott környezetet határoznak meg. Alkalmazásukra függvények hívásánál leht szükség. A paraméterezés funkciója, hogy a függvényeknek adatokat adjunk át. Két féle függvény létezik: a típusos és a típus nélküli (void) függvény. Típusos függvény meghívásakor egy bizonyos típusú értéket ad vissza, a típus nélküli függvény nem ad vissza értéket. Ez utóbbit bizonyos nyelvekben eljárásnak is szokták nevezni. Itt, assembly nyelven nincs értelme eljárásokról beszélni, egy függvényt általában azért készítenek, hogy visszaadjon egy értéket.

Lehet szó például olyan függvényről, ami nem kér bemenő adatot, csak szolgáltat. Ilyen például egy getc() függvény, ez a billentyűzetről olvas egy karaktert. De lehet írni olyan függvényt is - például a szorzas() -, aminek megadunk két értéket és a függvény ezek szorzatával tér vissza. Ezek a megadott értékek a függvény paraméterei. Amennyiben a függvény paraméterei közvetlen értékek, érték szerinti átadásról beszélünk. Abban az esetben, ha a függvénynek változók címeit adjuk át, cím szerinti átadásról beszélünk. Nézzünk erre egy-egy példát.

 

Az első példában két 8 bites számot adunk össze, a 8 bites értékeket karakteres, vagy bájt típusú értéknek hívjuk. A függvény neve legyen szorzas(), és az szorzatot egy 16 bites (short integer) értékben adja vissza.

 

; szorzas(byte al, byte ah) : word(*AX*) {

szorzas:

push bx ; elmentjuk BX eredeti erteket

mov bl ah

mul bl

mov ax, bx ; AX-szel terunk vissza

pop bx ; visszatoltjuk BX eredeti erteket

; }

 

Azt az esetet, amikor az értékek regisztereken keresztül vándorolnak, gyors hívásnak (fast call) nevezik.

Paramétereket általában a vermen keresztül szoktak átadni. Intel processzoron két stílus terjedt el, a Pascal és a C stílus szerinti paraméterezés. Pascal esetén ha két paraméter van, akkor a verem tetején az utolsó paraméter értéke van, az alján pedig az elsőé. C stílus esetén ez fordított. Nekem mindegy, ki mit szokott meg, a nagy többség a Pascal stílust használja. Ez úgy vélem, feladatfüggő. Ha az utolsó paraméter egy ritka esetben használatos érték, szinte opcionális paraméter, mint például van egy szövegkiíró függvényünk, aminek utolsó paramétere lehet egy logikai változó, ami ha igazra van állítva (ezt úgy is mondhatjuk, hogy "be van billentve"), akkor a függvény kiír egy vigyort is ) a kiírandó szöveg végére. Nos, úgy vélem, ez a plusztevékenység a szövegkiírás, mint algoritmus szempontjából a leglényegtelenebb, és szinte oda nem illő komponens. Mégis Pascal stílusú átadásnál ezt a logikai változót kapja meg először a rutin ahelyett, hogy ezt kapná meg utoljára, mint C stílusban.

Akkor lássuk, hogyan is néz ki az első feladat C stílusban átadott paraméterekkel:

 

mov al 4 ; negy

mov ah 9 ; szorozva kilenccel

push ax

call szorzas

pop ax ; ezutan AX-ben lesz az eredmeny

 

 

S íme, a függvény:

 

; szorzas(byte, byte) : word {

szorzas:

mov bp, sp

mov al, ss:[bp + 2] ; szorzando

mov bl, ss:[bp + 3] ; szorzo

mul bl

mov ss:[bp + 2], ax ; ax erteke vissza a stackbe

ret

; }

 

Ez azért hagy némi kívánnivalót - de legalábbis magyaráznivalót - maga után. Mielőtt a függvényt meghívtuk, két 8 bites értéket elhelyeztünk a verembe. Amikor a függvényt meghívtuk, a visszatérési címet is letolta a "call" utasítás. Ez egy 16 bites cím, mert a processzor valós módjában dolgozunk. Vagyis ezt a 16 bitet át kell lépnünk, hogy a paraméterekhez hozzáférjünk. 2 bájttal lemászva a verembe, megtaláljuk az egyik 8 bites paramétert, ez lesz a szorzandó; még egy bájttal, vagyis 3 bájttal lejjebb pedig a másikat, a szorzót találjuk. Elvégezzük a szorzást, az eredmény "ax" regiszterbe kerül. Ezt visszaírjuk a verembe, a 2. bájttól, hiszen ott van szabad hely. Ezt az értéket egy külső, hívó program, mely ezen függvényt hívja, a "pop" utasítással kiveheti a veremből. Miért is? A "ret" hatására a verem tetején lévő visszatérési cím beíródott IP-be, vagyis a vezérlés visszaugrott a hívó félhez, majd pedig minden feljebbugrott a veremben 16 bittel, mert egy 16 bites IP regiszter érték volt a verem tetején. Ezáltal a verem tetejére új érték, mégpedig a szorzat került. Ezt lehet onnan lehalászni a "pop" utasítással.

Egy fontos dolog: SP-vel közvetlenül nem lehet indexelni, mert Intel bácsi így találta ki. Az szerinte nem arra való... Jó, hát ha nem, hát nem. De az Assembly azért van, hogy átverjük a processzor agyát: BP-vel lehet indexelni, akkor mázoljuk be BP-be SP értékét, aztán adjunk neki... Gonosz, mi?

 

Kifejezések, operátorok

 

A kifejezés operátorok, változók, elnevezett és nevezetlen literálok olyan kombinációja, mely egy értéket ad eredményül. Ilyen kifejezés például a (4 + 3). Értéke, mint tudjuk, 7. Ilyen kifejezés a (2 > 4) is, ennek értéke HAMIS. Ez utóbbi egy relációs, logikai értéket adó kifejezés. A ("cica"+' '+"mica") értéke "cica mica": ez egy szöveges kifejezés. Gyűjtsük össze, milyen kifejezések lehetnek.

 

aritmetikai kifejezés

3 + 4 értéke 7

(3 * 4) - 27 értéke -15

a=2; b=4; c=5; értékadások

((a + b) / 2) + c értéke 8

i << 3 balra léptetés hárommal,

vagyis szorzás 2^3-nal

 

relációs kifejezés

a < 2 igaz, ha a értéke kisebb, mint 2

q == 3 igaz, ha q értéke egyenlő 3-mal

 

logikai kifejezés

b & c igaz, ha b értéke ÉS c értéke is igaz

(a | b) & (c | d) igaz, ha a VAGY b igaz ÉS c VAGY d is igaz

 

szöveges kifejezés

'a' + ' ' + 'b' "a b"

"ma" + "ci" "maci"

 

Az operátor egy műveletvégző nyelvi egység. Az egyoperandusú operátorral egy értéken végzünk műveletet, ilyen a -- és ++ operátor, mert ezek egy értéket rendre csökkentenek, illetve növelnek eggyel. A kétoperanusú operátorok két értéken dolgoznak, s egy harmadik értéket képeznek belőlük. Ilyen például a + operátor, mert például a (3 + 4) esetében két értékkel, a 3-mal és a 4-gyel operálnak, ezeket összeadják, s harmadik értékként az összeget szolgáltatják, 7-et.

 

A következő táblázat az operátorokat tartalmazza és példákat ad rájuk. Az első oszlop az operátor jele (C szintaktikában, amit ugyan nem hátrányos megtanulni annak ellenére sem, hogy az Assemblerek nem ismerik), a második az operátor jelentése, a harmadik az Assembly mnemonic (vagyis az operátor kulcsszava Assembly nyelven). A negyedik oszlop néhány példát tartalmaz.

Operátor

Jelentés

Mnemonic

Példák

=

 

legyen egyenlő

Mov

Mov al,3

Mov eax,ebx

+

Összeadás

Add

Add al,5

-

Kivonás

Sub

Sub al,7

++

Eggyel való növelés

Inc

Inc si

--

Eggyel való csökkentés

Dec

Dec bp

*

Egész szorzás

Mul

Mul ebx

/

Egész osztás

Div

Div ebx

>>

Jobbra tolás

Shr

Shr edx,1

<<

Balra tolás

Shl

Shl edx,1

(nincs)

Jobbra forgatás

Ror

Ror eax,1

(nincs)

Balra forgatás

Rol

Rol eax,1

(nincs)

Jobbra forgatás carry-vel

Rcr

Rcr edx,1

(nincs)

Balra forgatás carry-vel

Rcl

Rcl eax,1

 

Mi az a shiftelés, vagyis tolás? Végezzünk el példaként néhány műveletet az alábbi 8 bites számon: 01011011b (a 'b' utótag a szám bináris alakjára utal). Toljuk jobbra, az eredmény 00101101b lesz, és a CARRY (átvitel) bit értéke 1, mivel ez a kicsúszó bit értéke. Balra tolva az eredmény 10110110b és a CF (Carry Flag) most 0. Jobbra forgatva 10101101b lesz, mert a jobb oldalt kilépő bit nem csak CF-be kerül, hanem visszaíródik bal oldalra, a szám legfelső bitjeként. Balra forgatásnál tehát 10110110b lesz az érték és CF 0 lesz.

A forgatás másik fajtája, a Carry-n kereszüli (through carry) forgatás annyit jelent, hogy először beállítjuk a carry értékét pl 1-re, majd ha elvégzünk egy carry-s jobbra forgatást (RCR), akkor 01011011b-ből 10101101b lesz és carry értéke is most 1 lesz, mert ennyi csúszott ki. Vagyis olyan, mintha 9 bittel végeznénk műveletet.


 

Operátor

Jelentés

Mnemonic

Példák

&

ÉS

And

And al, 1010b

|

VAGY

Or

Or bl, 1010b

^

Kizáró Vagy

And

Xor dl, cl

!

Tagadás

Not

Not eax

-

Előjelváltás

Neg

Neg eax

 

Csak a NEG műveletre térnék ki, a többi úgy érzem, magától értetődő. A "neg" művelet egyoperandusú, és a megadott regiszter értékét negálja, vagyis mínusz eggyel szorozza: a kettes komplemensét képezi.

 

 

A következő részben már konkrét, egy az egyben fordítható és futtatható programokkal (ujjgyakorlatok és barátai) szeretném a tisztelt olvasóközönséget boldogítani ...

 

Németh Róbert - nrobcsi@freemail.hu