A InfoWiki wikiből
Aroan (vita | szerkesztései)
(Új oldal, tartalma: „<cim cim3="2.generációs programozási nyelvek" cim2="Magasszintű Programozási Nyelvek I." cim1="Imperatív, procedurális nyelvek alapjai" prev="mp1/page002" n...”)
Újabb szerkesztés →
A lap 2009. október 15., 09:05-kori változata
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.
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:
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:
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:
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:
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:
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:
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.
Ö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.