Személyes eszközök
Keresés

 

A InfoWiki wikiből


Második generációs programozási nyelvek

A gépi kódú programozási nyelv hátrányai miatt új nyelvet kellett kifejleszteni.

Az ASSEMBLY nyelv első közelítésben a megérthető gépi kód nyelve. Az utasítások számkódjait néhány betűs (2-3-4 betűs) ún. mnemonikokkal helyettesítették. Egy ilyen mnemonik (emlékeztető szócska) a gépi kódú utasítás jelentéstartalmára utalt. Például a „memória-tartalom beolvasása egy regiszterbe” (bemozgatás) az angol MOVE=mozgatni szó alapján a MOV mnemonikot kapta. A „két szám összeadása” az angol ADD=összeadni mnemonikot kapta. Az 1. ábrán a harmadik oszlopban szerepelnek a gépi kódú utasítások assembly nyelvű megfelelői.

A MOV utasítás önnmagában nem lefordítható gépi kódra, hiszen azt is meg kell adni, hogy melyik memória-cím tartalmát kell betölteni melyik regiszterbe. Az utasítás egyik lehetséges formája „MOV EAX,<memcím>”. Ennek már egyértelműen megfelel egy gépi kódú utasításkód (mov eax = 8B), a memóriacímet pedig a további számkódok írják le.

Image:Abra02.png
Assembly nyelvű programrészlet


Ennek megfelelően az assembly nyelv egy adott mikroprocesszor adott gépi kódjához készült el. Ezért az assembly nyelv is processzor-függő, de ezen a szinten újabb fogalmak jelentek meg:

Forráskód: az assembly nyelvű programot a CPU nem képes közvetlenül megérteni és végrehajtani. Az assembly nyelvű programot egy szöveges file-ban kell megírni (forráskód), majd le kell fordítani gépi kódú, ún. tárgyprogramra (object code).

Fordítás: az a folyamat, amely közben a forráskódot egy fordítóprogram (compiler) gépi kódú utasítások sorozatára transzformálja.

Az assembly nyelv esetén ezt a fordítóprogramot ASSEMBLER-nek nevezték. Az assembler végigolvasta a forráskódot sorról-sorra, e közben generálta a gépi kódú utasítássorozatot - a mnemonikok alapján előállította a megfelelő gépi kódú utasítás számkódját. Az assembler nem bonyolult program, hiszen a fordítás egyszerű szabályok szerint működik.

Az ASSEMBLER a forráskód feldolgozása közben egyszerű ellenőrzéseket is elvégez. Amennyiben valamely mnemonikot nem ismeri fel (pl. a programozó elgépelte), akkor a fordítási folyamat leáll, az assembler hibaüzenettel jelzi a hiba okát és helyét a forráskódban. A forráskódban az olyan jellegű hibákat, melyek súlyossága folytán a fordítás nem fejezhető be – fordítási időben történő hibának nevezzük. Ennek oka általában a nyelv szabályai (szintaktikája) elleni vétség, ezért ezt szintaktikai hibának is nevezzük.

Az assembly nyelvű programok forráskódja olvashatóbb, mint a gépi kód, illetve könnyebben módosítható. Az assembler a program szintaktikai helyességét ellenőrzi le, emiatt az első eszköznek is tekinthetjük, amelyet a programozók munkájának segítésére (is) alkottak.

A fordítás ellenkező művelete a de-compiling. Ennek során a gépi kódú program-változatból a programozó megpróbálta visszaállítani annak assembly nyelvű eredetijét. Erre sokszor azért volt szükség, mert az eredeti forráskód elveszett, de a programon mégis módosítani kellett. A gépi kódú programok módosíthatóságának nehézségét jelzi, hogy az assembly-programozók már nem voltak hajlandók közvetlenül a gépi kódot módosítani, inkább helyreállították az assembly forráskódot, abban elvégezték a módosításokat, majd újra lefordították (generálták) a gépi kódú programot.

Ez a művelet persze némi veszteséggel járt – hiszen a tényleges eredeti assembly forráskódban többek között megjegyzések is lehettek. Ezek a megjegyzések a gépi kódú változatba nem kerültek bele, ezért visszaállítani sem lehet őket.

Ami viszont nagyon fontos lépés – a fordítóprogramok megjelenésével megjelent az igény ezek intelligenciájának fejlődésére!

Változók

Az assembly programokban a memória-címekre azok sorszámával lehetett hivatkozni. Egy 128Mb memóriával szerelt számítógépben 128*1024*1024 db sorszámot lehet használni, mivel a memória ennyi db byte-ot tartalmaz. A példában ezek a sorszámok a [0 .. 134.217.727] intervallumból kerülnek ki .

Az assembly programok sokszor tényleges memóriacímeket (sorszámokat) tartalmaztak számkonstansok formájában. Ez sok szempontból nem volt elég rugalmas:

  • A számok nem hordozzák a jelentésüket, nehezen volt kiolvasható a programból, hogy a 1034-es memóriacímen milyen adat van, mi az ott lévő érték jelentése.
  • A programban e memóriacímek sokszor előfordultak. Ezért a memóriacímek nem voltak könnyen módosíthatóak.
  • A memóriacím alapján nem dönthető el, hogy az ott tárolt adat hány byte-ot foglal el, ugyanis a memóriacím csak a memóriabeli kezdőcímet jelöli.

A helyzet javítása érdekében a programozók elkezdtek konstansokat használni a programjaikban. Ezeket a programok elejére összefoglaló jelleggel, táblázat-szerű formában írták le:

Image:Abra03.png
Memóriacímeket leíró konstansok


Ezek után az assembly programokban az ADD EAX,[1034] helyett (ami azt jelentette, hogy add hozzá az [1034] memóriacímen található értéket az EAX regiszterben éppen benne levő értékhez) azt írhatták, hogy ADD EAX,[FIZETES]. Ez sokkal olvashatóbb forma, másrészt amennyiben a fizetés értékét mégsem az 1034-es memóriacímen kellett tárolni (változás), akkor egyetlen helyen kellett csak átírni – a program eleji táblázatban.

Ez az egyszerű és logikus meggondolás indította el a ’változó’ fogalmának fejlődését. A fenti esetben a FIZETES volt az azonosító, melyhez a programozó konstans formájában rendelt hozzá memóriacímet, így foglalva helyet a memóriában.

Még fejlettebb assembler fordítók esetén a fenti táblázat az alábbi formában is felírható volt:

Image:Abra04.png
Inkrementális felírás


Ebben az esetben már kiolvasható a táblázatból, hogy a FIZETES névvel jelölt memóriaterület 4 byte-os tároló hely, hiszen ekkora memória-szakasz fenntartása után a következő memóriaterület (’ELETKOR’ elnevezéssel) 4 byte-tal távolabbi ponton kezdődik.

Ez a változat azért is jobb, mert segíti, hogy elkerüljük két tárolóterület átfedését a memóriában (átlapolás). Megakadályozni nem tudja, hiszen amennyiben a fizetés tárolási igénye 8 byte lenne, akkor a fenti esetben az életkor még átlógna a fizetés tárlóhelyének utolsó 4 byte-jára. Ennek persze a programozó az oka, aki rosszul írta fel a táblázatot. Ha azonban a fizetés tárolására 2 byte is elég lenne, akkor 2 byte-nyi felhasználatlan terület keletkezne a memóriában (ami szintén nem szerencsés).

A fenti problémákon túl az assembler azt sem tudta ellenőrizni, hogy a tárlórekeszt megfelelően kezeli-e a programozó a későbbiekben:

Image:Abra05.png
Lehetséges hibás használat


A fenti utasítások mindegyike elfogadható az assembly nyelv szintaktikai szabályai szerint. Az első esetben a megadott memóriacímről 4 byte-nyi adat kerül be az EAX nevű regiszterbe. Második esetben csak 2 byte, harmadik esetben csak 1 byte. A memória-beolvasást ugyanis a fogadó regiszter mérete befolyásolja. Amennyiben a fizetés 4 byte-on kerül tárolásra, úgy a második és harmadik utasítás nagy valószínűséggel hibás, hiszen miért olvasnánk be a fizetést tartalmazó számsorozat-nak csak az egyik felét?

Az ilyen jellegű hibákra azonban az assembler nem tudott figyelmeztetni, mivel számára a FIZETES csak egy memóriacím volt. A helyes kezelésre a programozónak kellett ügyelnie. Az assembler nem is tudta ellenőrizni a kódot, hiszen nem volt információja arról, hogy mit is ért a programozó fizetés alatt.

Ezt a plusz információt hívják a programozási nyelvben típusnak.

A típus sok mindent meghatároz. Többek között meghatározza az adott érték tárolási méretét a memóriában. A típus ismeretében a fenti táblázat felírható az alábbi formában:

Image:Abra06.png
Típusnevek használata


A fenti táblázat szerint a FIZETES egy dupla-szó (DWORD), aminek helyigénye 4 byte. Az ELETKOR egy szimpla szó (WORD), 2 byte helyigényű. A NYUGDIJAS egy byte, aminek helyigénye (mint a neve is mutatja) 1 byte.

Ha az ASSEMBLER már a fenti táblázatot is kezeli, akkor már képes a tárterület-címeket automatikusan kiosztani. Az első azonosító címéhez képest a primitív típusnevek (dword, word, byte, …) tárolási igényét ismervén automatikusan növeli a memóriacímeket, és ad értéket az azonosítóknak. Ezek az értékek továbbra is memóriacímek, de az automatikus kiosztás miatt a memóriaterületek átlapolásának esélye még kisebbre zsugorodott.

A fenti primitív típusnevek még nem jelölték az azonosítók tényleges típusát. A fordító mindössze a tárigény-szükséglet kiszámítására használta fel. A hibás kezelés lehetőségét még mindig megengedte, vagyis egy 4 byte-os helyigényű értéknek még mindig szabad volt az egyik felét beolvasni, és dolgozni vele.

Nem voltak ’finom’ típusnevek. Nem volt ’char’, ’bool’, ’short int’, ’unsigned short int’ típusok. Ezek mindegyike 1 byte tárolási igényű, csak ezen 1 byte-on hordozott információ jelentésében térnek el. Mivel a jelentést a fordító még nem kezelte, csak egy olyan típusnév volt, amelynek 1 byte a tárolási igénye.

Ennek megfelelően ezt még nem tekinthetjük tényleges típusnévnek, mindössze tárolási-igény névnek.

A változó más komponenseinek (élettartam, hatáskör) kezelése is hiányzott az assembly nyelvből, és az assembler programokból.

Vezérlési szerkezetek

Az assembly nyelv másik szegényes tulajdonsága a vezérlési szerkezetek hiánya. Lényegében csak az alábbi vezérlési szerkezetek találhatóak meg:

  • Szekvencia: a program utasításainak végrehajtása a memóriabeli sorrend alapján történik.
  • Feltétlen vezérlésátadás (ugró utasítás): a program folytatása egy másik memóriabeli pontra tevődik át, majd attól a ponttól kezdve a végrehajtás újra szekvenciális.
  • Feltételes vezérlésátadás (feltételes ugró utasítás): mint az egyszerű ugró utasítás, de az ugrást csak akkor kell végrehajtani, ha az előírt feltétel teljesül.
  • Visszatérés az ugró utasítást követő utasításra (a vezérlésátadást követő utasítás címére).

Ezekből kellett összerakni a programot. Egy egyszerű elágazást ennek megfelelően az alábbi módon kellett kódolni:

Image:Abra07.png
Elágazás gépi kódban


A fentiekből talán sejthető, hogy az assembly nyelvű programból kibogozni hogy itt valójában feltételes elágazás történt – nem egyszerű. Hasonló problémákkal jár a ciklusok megtervezése és kódolása is – különösen az egymásba ágyazott ciklusok esetén.

Alprogramok

Az assembly nyelv elvileg lehetőséget ad eljáráshívásra is az alábbi formában:

Image:Abra08.png
Lehetséges hibás használat


A kiiras itt valójában címke (programsort jelölő név), de megfelel az eljárásnév primitív fogalmának. A nevesített címkék egy jó névválasztással, nagyon sokat könnyítenek a programkód olvashatóságán.

A probléma nem is itt rejtőzik, hanem hogy az eljárásnak hogyan adunk át paramétereket? Illetve, ha ez nem eljárás, hanem függvény, akkor hol kapjuk meg a visszatérési értéket? Honnan tudjuk, milyen típusú adattal, illetve értékkel tér vissza az adott függvény?

A fenti kérdésekre a válaszokat maga az assembly nyelv nem tartalmazza. Paraméterek átadására például több mód is van – csakúgy mint a függvények visszatérési értékének visszaadására. Ezeket a lehetőségeket maga a gépi kód tartalmazza, és az assembly nyelv értelemszerűen átvette.

A programozók szívesen fejlesztettek általános, újra felhasználható eljárásokat és függvényeket, melyek segítségével a programok fejlesztési ideje, és a tesztelési ideje is alaposan lerövidült. Ezek egymással való megosztása a fenti akadályok miatt nehézkes volt. Egy másik programozó által fejlesztett, nem megfelelően dokumentált eljárás felhasználása a kódban néha több energia felemésztésével járt, mint újra megírni a szóbanforgó eljárást.

Több modul

A több modulra tagolás azért volt fontos, mert az assembly programok általában nagyon hosszúak, másrészt az akkori számítógépek még nagyon lassúak voltak. Egy hosszú forráskód fordítása nagyon sok időbe került.

A fejlesztés során, természetes módon program szövegében olyan szakaszok keletkeznek, amelyekkel a programozó már elkészült, és továbbiakban már nem kívánja módosítani. E szakaszok újrafordítása során ugyanazon tárgykód generálódik újra és újra. E szakaszok újrafordítása az assemblerrel minden egyes alkalommal felesleges idő és energiapocsékolás lenne.

Ezért bevezették a több modulból történő fordítást. Ekkor az assembler a fordításhoz egy listát kapott, hogy mely forráskód-fileokból tevődik össze a projekt. Az assembler a forráskód-modulokat egyenként fordította le egy-egy tárgykód (object) állományba. Amennyiben a forráskódon nem történt módosítás, úgy azt az assembler egy gyors ellenőrzéssel észrevette, és nem generálta újra a hozzá tartozó object kódot. Így csak azon forráskódok fordítása történt meg, melyek változtak az utolsó fordítás óta. Miután az assembler végzett a forráskódok fordításával, egy másik, speciális feladatot végző program következett, a szerkesztő program (linker). A linker a sok apró kis tárgykód alapján készítette el a működőképes programot .

Ez jelentősen meggyorsította a fordítást. Sőt, a programozók az újra felhasználható, általános eljárásaikat külön kis kód-gyűjteményben tárolták, és újabb project kezdése esetén eleve hozzácsatolták a projekt forráskód-listájához.

Ez tovább erősítette a szándékot az általános célú eljárások írására, és egymás közötti megosztásra. Az assembly nyelv ez irányú képességeinek hiánya ebben továbbra is komoly gátat jelentett.


A fentiek bizonyítják, hogy az assembly nyelv sok olyan lehetőséget rejtett magában, amely miatt megérdemli a külön generációs besorolást. Ugyanakkor a nyelvi korlátok gátolták a programozási stílus fejlődését.


Összefoglalás

Új fogalmak:

  • forráskód
  • fordítóprogram
  • fordítás
  • szintaktikai ellenőrzés
  • típus-nevek
  • eljáráshívás
  • tárgykód
  • több modul
  • szerkesztőprogram
  • futtatható kód

A szintaktikai ellenőrzések körének bővítéséhez a programozási nyelvet magát is át kell alakítani. Ezzel persze a programozók egyre szűkebb lehetőségből választhatnak, egyre szigorúbb ellenőrzéseken esik át minden, amit leírnak. Cserébe kódolási hatékonyságot és biztonságot kapnak.

Hernyák Zoltán
A lap eredeti címe: „http://wiki.ektf.hu/wiki/Mp1/page003
Nézetek
nincs sb_100.24.125.162 cikk