Elméleti alapozás
Interpreter-compiler-linker-bytekód és egy kis pinjin-átírás
Az előző, A Java-nyelv jellegzetességei című fejezet elnagyoltan foglalkozott a programozási nyelv egyik fontos részével, a nyelvfordítás bonyolult folyamatával. Bevezető gondolatként először ismételjük meg idevágó megállapításainkat:
-
interpretált (soronként értelmezett) - az értelmező (interpreter) beolvas egy utasításegységet a forráskódból, azt végrehajtja, majd újra olvas. A soronkénti értelmezés miatt általában lassú futású.
-
fordított (compiled) - egy speciális program, a compiler a forráskódból egy közvetlenül futtatható állományt készít, általában egy köztes állapoton keresztül, úgynevezett linkeléssel.
-
fordított (compiled) és interpretált - ötvözi a fenti kettő előnyét, viszonylag gyors futást eredményezve és a fordítási idővel sem kell törődni.
Ebben a fejezetben tehát ezt a folyamatot magyarázzuk tovább.
Azt hiszem egyetértünk, ha azt állítom, hogy a fordítás szóról mindenkinek először a különböző élő nyelvek közti fordítás jut eszébe, például:
"To be, or not to be, that is the question: - Lenni vagy nem lenni: az itt a kérdés..."
Ám a fordítás szintén remek szótalálat arra az informatikai folyamatra, amely során egy logikusan összeállított, azaz gondolatközpontú, de a számítógép által értelmezhetetlen programkódból (forráskódból) számára értelmezhető utasítássorozat jön létre (gépi kód, natív kód, tárgykód).
Nézzünk erre egy példát (a bináris számok csak fikciók, de jellegük szerint ilyen utasítások töltődnek be a számítógép memóriájába, hogy onnan a processzorba továbbkerülve végrehajtódjanak):
1 + 1 = 2
↓
00000001
00000011
01101111
01110001
00011000
.exe
Az interpreter-compiler-linker nevű programok tehát azok az automata nyelvtolmácsok, amelyek a fordítás minden gondját leveszik vállunkról.
A továbbiakban alapszinten kifejtjük működésüket, amely során képzeljük el, hogy magyarról kínai nyelvre kell szöveget fordítaniuk, például:
Shanghaj
↓
Editor
A forráskódot fordítás előtt adott programozási nyelven először meg kell írni lehetőleg olyan digitális felületen, ahol a forrásszöveg kevésbé "szennyeződik" egyéb információkkal. Például MS Word szövegszerkesztővel megírt forráskód rengeteg további belső, formázási utasítást tartalmaz, amely miatt a compiler már rosszul fordítaná le a szöveget és/vagy számos hibát jelezne, hasonlóan ahhoz, ha a lefordítandó magyar szövegbe valamilyen módon érthetetlen szavak keverednének. (Ilyen hibajelenségről részletesen írok a Visszatérő szimbólumok és jelzések című fejezetben.) Igazából minél egyszerűbb a szövegszerkesztő, annál jobb, mert annál kevésbé "koszol" (például MS Notepad):
Compiler
Ez a fordítóprogram egy az egyben lefordítja a forráskódot gépi kódra éppúgy, amint a magyar szöveget kínaira tenné át, ám fontos kihangsúlyozni, hogy vissza már nem tudja fordítani. (Később látni fogjuk, hogy ezt csak köztes bytekód esetén tudjuk megtenni.) Sok fordítóprogram további extrákat is biztosít:
-
nagyobb léptékű (projektszintű) kódszervezés,
-
hibakeresés és javaslat,
-
járulékos információk rendszerezett figyelése, menedzselése (például megjegyzésekből help-, és/vagy dokumentációs állomány állítható össze),
-
stb.
A compiler által generált kód azonban még nem futtatható, mert nem teljesen illesztett ahhoz a programozási környezethez, amelyben futtatni szeretnénk. Konkrétan ez mindig maga az operációs rendszer, hiszen a keletkezett kód futtatását ő végzi el.
Ismétlés: azt hiszem, az eddig magyarázottak alapján már felfedeztük, hogy az operációs rendszer is egy rendkívül összetett programcsomag, amelynek elsődleges feladata a hardver életre keltése és biztonságos működtetése a felhasználó vagy egyéb igényekhez igazodva.
Linker
A végső, az operációs rendszerhez kötődő konkrét cím-, adat-, és műveleti illesztések a linker feladata. Compiler és linker együtt dolgoznak, ezért legtöbbször együtt is nevezik őket, illetve összefüggő fordítási programkörnyezetet alkotnak.
A magyar-kínai analógiában a linker az a kínai kishivatalnok, aki a keletkezett fordítást helyi aktualitásokkal látja el: lepecsételi, megcímezi, talán még bele is írogat, hogy nyelvezete ne legyen annyira hivatalos vagy a helyi nyelvjáráshoz igazítja. Magyarán (vagy kínaian): ő illeszti be a fordítást a konkrét környezetbe.
Compiler és linker együttes munkája során a fordítás több, de gyors lépésben, lényegében egyszerre, 1 munkafolyamat során keletkezik. Vegyük észre, hogy az így keletkezett programkód bizony rendszerfüggő, azaz nem vihető át másik típusú operációs rendszerre, tágabb értelemben szoftverkörnyezetre.
Interpreter
Az interpreter fordító annyiban különbözik a fentiektől, hogy ő nem egyszerre, hanem utasításonként fordít natív, gépi kódba.
Modellezzük ezt pszeudokóddal, bár itt azért nincs definiálva, hogy mit értünk 1 utasítás alatt! Valószínűleg tartom, hogy ez processzortípusonként különbözhet. Emlékezzünk arra, hogy míg a compiler-linker ilyesféle kódfordítást végzett...
1 + 1 = 2
↓
00000001
00000011
01101111
01110001
00011000
.exe
...addig interpreter úr gépiesen ilyesfélét tesz:
1
↓
00000001
+
↓
00000011
↓
01101111
↓
1
01101111
↓
= 2
stb.
Természetesen a fenti folyamat fikció; nagy valószínűséggel először memória-címkijelölés, majd memória-helyfoglalás történik, majd az adatok betöltése és így tovább. A fent mutatott szekvencialitás tehát elméleti, ám mégis megmutatja az interpreter és a compiler-linker közti működésbeli különbséget.
Kissé olyan az interpreter, mintha a magyar-kínai tolmács csak szavakat fordítana egymás után és nem érdekelné azon tény, hogy a szavak esetleg mondatokat is alkotnak. Az ilyen munka nyilvánvalóan lassúbb a compiler-linker produkciónál és szintúgy rendszerfüggő.
Bytekód
A .class kiterjesztésű bytekód, tágabb értelemben a Java nyelvfordítása érdekes ötvözése a interpreter-compiler-linker nyújtotta lehetőségeknek. A Java-compiler csak egy köztes, úgynevezett bytekódot állít elő:
Ez tökéletes átmenet a forráskód és a gépi kód között, leghasznosabb tulajdonsága pedig, hogy rendszerfüggetlen. Értelmezése, végső futtatása a JVM feladata, amelyet előzetesen minden olyan rendszerkörnyezetre fel kell telepítenünk, amelyen a Javát futtatni szeretnénk. Ez roppant mennyiségű előzetes munkát jelentett a Java rendszerprogramozóinak, hiszen a JVM illesztését az összes számbavehető operációs rendszertípusra előzetesen meg kellett oldani...
...ezzel azonban a Java óriási előnyhöz jutott: a JVM mindenhol, azaz minden platformon egységes szoftvermotorként, mintegy a rendszerek között elosztott virtuális gépként tud fellépni. Ezért lehetséges, hogy köztes bytekódok akár dinamikusan, hálózaton keresztül is betöltődhetnek, illetve, hogy a bytekódok vígan visszaalakíthatók a forrásállományba.
Ebből következik a talán legismertebb Java-mondás: írd meg egyszer, futtasd sokszor!
Nem véletlenül választottam a magyar-kínai hasonlatot. Kevesen tudják, hogy a kínai nyelvnek létezik egy hivatalosnak minősített, fonetikai átírása, az úgynevezett pinjin-átírás. Ezt alapjában véve bármelyik európai el tudja olvasni, bár nagy valószínűséggel olyan hanglejtéssel (a kínai nyelvben ez nagyon fontos), amely kínai számára érthetetlen:
Nos, a pinjin-átírás éppúgy tökéletes átmenet a nyelvek között, mint a bytekód. Közös jellemzőik:
a 2 nyelv között pontosan a logikai félúton tartózkodnak,
belőlük az alapnyelvek visszafejthetők, illetve tovább fordíthatók.