Assembler tanulmányok: Aritmetikai műveletek

Az előző írásban próbáltam bevezetni ezt az elég komplex nyelvet. Még mindig azt tartom, hogy úgy lehet legjobban elsajátítani egy programozási nyelvet, ha azt csinálja az ember. Így most aritmetikai (számolós) feladatokon keresztül bemutatok jó pár alkalmazást ezen a nyelven. A következő forráskódok egy-egy komplexebb számolási feladatot tartalmaznak, amit az ASM (pontosabban HLA) adta lehetőségekkel implementáltam Sobee barátommal együtt. A forráskódon túl itt pár dolgok kiemelek, amit jó tudni.

A változókat deklaráltunk, ezeket viszont hogy átmozgassuk egy regiszterekbe (jelen példában 32 bitesbe) máris egy függvényt kell elsajátítanunk. Ez a MOV(); utasítás. Két operandusa van. Először megadjuk azt amiből történjen az értékmásolás, majd azt amibe. Pl.: MOV(r,EAX); < -- Ez az utasítás az "r" változó értékét az EAX regiszterbe mozgatja át. Fontos kiemelni, hogy ezeknél az értékmozgatásoknál rendszerint konverziókat kell alkalmazni. Tegyük fel, hogy egy 16 bites változó értékét egy 32 bites regiszterbe akarjuk tölteni. A harminckét bites értékhez nem okos hozzányúlni, mert szinte biztos, hogy adatvesztéssel fog járni. Inkább a 16 bites számot kell kiterjeszteni. Na igen. Lehetőségünk van zéró kiterjesztésre, amikor nem vesszük figyelembe az előjelet ( MOVZX(); ), vagy előjeles kiterjesztésre, amikor az előjelet is figyelembe vesszük ( MOVSX(); ). ASM-nél az egyszerű műveleteknél, mint a kivonás vagy az összeadás lehetőségünk van parancsokból elrendezni az egészet. Egyetlen dologra kell figyelnünk, hogy a két szám amit össze akarunk adni, vagy kivonni, az mind a kettő azonos biten legyen ábrázolva. Az összeadáshoz az ADD(); utasítást a kivonáshoz a SUB(); utasítást használjuk. Mindkét esetben az eredmény a második operandusban fog tárolódni. Pl.: ADD(a,b); == b=a+b; és SUB(a,b); == b=b-a; A szorzás egy kicsikét nehézkesebb. Van MUL(); IMUL(); INTMUL(); utána lehet nézni mi pontosan milyen feladatra való, mi a feladat során a teljesen kiterjesztett INTMUL(); utasítást használtuk. Az eredmény ebben az esetben is a második operandusban tárolódik Pl.: INTMUL(EBX,EAX); == EAX=EBX*EAX. Az osztás az pedig a legrosszabb. A mi feladatunkban speciális maradékos osztást kellett végrehajtanunk. Ennél rosszabb nincs. Az IMOD(); egy operandusú utasítást kell használni. Ez egy nagyon speciális dolog lesz kérlek figyeljetek jól. Először is ki kell nulláznunk minden esetben az EDX változót. Ezt a SUB(EDX,EDX); utasítással tehetjük meg. Az osztandó számot kötelezően az EAX változóba kell tennünk. Ezek után jöhet az IMOD. Egész pontosan az IMOD egyetlen operandusának az osztót kell megadni, azaz azt a regisztert amiben az osztó értékét tároljuk. Az utasítás lefutása után az EAX regiszterben fog tárolódni a hányados egész része, az EDX változóban pedig a maradék. (Amennyiben csak egész részre vagyunk kíváncsiak ugyan ez az eljárás az IDIV(); utasítással, csak abban az esetben az EAX regiszterbe kerül az érték és EDX-re nincs szükségünk.) A forrást böngészve még lehet találkozni hasonlító műveletekkel. Azaz van egy kifejezésünk (PL.: a+b>10) és igaz és hamis esetekre is vannak utasítások megadva. Itt jön a kibővített ASM eszköztár, ugyanis ezt egy egyszerű IF-el meg tudjuk oldani. A CMP(); két operandusú utasítást kell először használnunk. Első operandust hasonlítja össze a második operandus értékével hogy nagyobb-e. Ezek után egy IF (@G) THEN … után kell felsorolnunk azon parancsokat, amik igaz ág esetén kell, hogy lefussanak ELSE ág után pedig értelemszerűen a hamis értékekre kívánt parancsokat. Végül egy ENDIF; utasítással zárjuk le.

Fontos még megemlíteni az abszolút értékek előállítását. Nagyon egyszerű ez is. Először is fontos, hogy MOV(); utasítással a második operandusba legyen az érték aminek az abszolút értékét akarjuk venni (ez működik ADD();-ra is). Ezek után egy IF (@S) THEN … ENDIF; vizsgálattal megnézzük az érték előjeles e. Ha igen, akkor belép a THEN ág mögött lévő utasításokra, itt pedig elég egy NEG(); egy operandusú utasítás, ami nem csinál mást mint a megadott regiszter értékének a negáltját (-1 szeresét) veszi. (Mi nagyon sokat szívtunk azzal, hogy egy 16 bites regiszter kiterjesztett 32 bitesre mindíg memória hibát dobott. Azóta se tudjuk miért viszont egy ADD(0,BAJOS_REGISZTER); megoldotta.)

Fontos még megemlíteni a ciklusszervezéseket. Itt is megtalálható a REPEAT…. UNTIL (kifejezés); WHILE (kifejezés) …. ENDWHILE; ciklusok, mégis én a jó öreg FOR ciklust fejteném ki jobban. A FOR ciklus kb így néz ki:

FOR(kezdő_érték_adás; kifejezés; inkrementálás;) DO
….
ENDFOR;

A FOR után három dolgot kell beírni. Első egy kezdő érték adás, a második egy kifejezés. A kifejezés igazra kiértékelése esetén lép a DO utáni utasításokra. A harmadik pedig a kezdőérték növelése (vagy csökkentése), olyan mértékben ahogy a feladat ezt megköveteli. Mint látszik a FOR az egyik legtömörebb ciklus, a többivel is megoldható ugyan ez csak hosszabban, néha viszont sebességnövelés érdekében hatékonyabb a többi módszer.

Még egyetlen dolgot akarok megemlíteni, a tömbökre hivatkozást. A tömbökre cím alapján tudunk hivatkozni. A tömb kezdőcíme lesz a tomb[0] azaz a tömb első (azaz nulladik) eleme. Ezek után a tomb[1]-re a tömb második elemére így tudunk hivatkozni: [EDI+4]
Na ez egy kis magyarázatot igényel. Először is feltételezzük azt hogy egy MOV(); utasítással a tömb kezdő címét az EDI változóba tettük már. Ezek után mik a kapcsos zárójelek? Ha egy regisztert kapcsok közé teszünk, akkor egy címfeloldást hatunk végre. Ebben az esetben nem az EDI változó értéke a lényeg, hanem azt egy memória/regiszter címnek tekintünk, és hogy azon a címen lévő adatokkal tudjunk foglalkozni, ahhoz kell a kapcsol zárójel. A „+4” pedig azért kell, mert a mi esetünkben egy 32 bites (négy bytes) tömbről van szó azaz négy címnyit kell ugranunk, hogy ott újra egy tömb elemet találjunk. Értelem szerűen a négy helyére a négynek annyi szorosát kell vennünk ahányadik elemére kíváncsiak vagyunk. Fontos viszont, hogy 0. elemmel kezdünk, ezt nem elfelejteni.

Ennyit tudtam magyarázatként hozzáfűzni. Lássátok a két-két példa. Mindkét fileban két feladatot kell megoldani. Első esetben a Fg11 két bonyolultabb egymásra épülő kifejezést kell végigszámoltatni. A Fg12-ben pedig egy tömb első két kezdőértékének kiszámolása után a megadott algoritmus szerint feltölteni a tömb elemeit, majd az elemek összegét visszaadni. Első példa, második példa.

Kérdést, panaszt bármit hozzászólásoknál lehet megtenni.

Assembler tanulmányok: Aritmetikai műveletek” bejegyzéshez 9 hozzászólás

  1. Á köszi szépen az infókat! Nagyon hasznosak voltak! PTM-es vagy/voltál nem? És HG-nél csináltad az Assemblert nem?
    Még egyszer köszi önzetlenséged!

    Üdv,
    Ádám

  2. Csao,

    Orulok ha hasznos volt. Nem PTM-es vagyok, de Halasz „bacsihoz” jartam akkoriban. Jo is hogy szolsz, mert van meg par feladat, amit fel akartam tenni, bar sajna mar regebben volt, talan olyan hazsnos es pontos leirast nem nagyon tudok hozza mellekelni, viszont becommentezve soronkent be lesznek. Tehat remelem hamarosan meg olvashacc hasznos Assembler segitsegeket!

    A CDQ fuggveny szukseges az osztasok ele, vhogy az terjeszti ki mas regiszterekre, tehat kulon kerul a maradek es az egesz resz!

  3. Hehe… Orulok, anno ezert oltem bele annyi faradtsagot, hogy meg egyszer vkinek jol jojjenek. Remelem egyszeru lesz atmenned… Sok sikert! :)

  4. Hat remelem akkor meg sok hasznos dolgot fox talalni rajta… Bar tanulmanyaim vegeztevel most nem epp fejlesztes iranyba probalkozom… :) De meg barmi lehet :)

  5. szia! Az lenne a kérdésem, ha egy változó 16 bites, egy konstans pedig
    32 bites, hogyan lehet velük műveleteket(összeadás,kivonás,szorzás)végezni? Mert csak olyanokkal lehet, ami azonos méretű Intmul, illetve Sub és Add utasítással…
    Válaszod előre is köszi, nagyon kelle a segítség, csütörtökre
    progikat kell beadnom!

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük