Was ist nun ein Compiler?
Welche Aufgaben muß ein Compiler erfüllen?
Was ist der Unterschied zu einem Interpretierer?
Welche Designentscheidungen muß man treffen?
Diese Fragen wollen wir in diesem Abschnitt kurz anreißen.
Man kann das Thema Compilerbau grob in vier Teilbereiche gliedern, die wir uns, soweit es die Zeit erlaubt, alle ansehen werden:
Das Zusammenspiel dieser Teilprogramme zeigt das folgende Bild:
Das Thema Scanner beschäftigt sich mit der Frage, wie man aus
den einzelnen Zeichen des Quelltextes die einzelnen Programmsymbole
extrahiert.
Der Parser überprüft dann diese Symbolfolge auf ihre syntaktische
Korrektheit. Ein Programm kann sehr wohl syntaktisch korrekt sein -
damit ist noch lange nicht gesagt, daß der Compiler es auch übersetzen
kann: denken Sie an Typkonflikte, undeklarierte Variablen, fehlende
Parameter, etc.
Diese Kontextbedingungen müssen zusätzlich geprüft werden, ehe man
mit der Übersetzung beginnen kann. Man sagt auch, daß die
statische Semantik geprüft werden muß.
Das letzte Thema, die Codeerzeugung ist das am wenigsten formalisierte. Hier ist noch sehr viel Kreativität nötig, um wirklich guten, effizienten Code zu generieren. Es ist in vielen Fällen relativ einfach, die Anweisungen und Ausdrücke einer Programmiersprache in für einen Computer verständliche Häppchen zu zerlegen. Wenn es aber harte Anforderungen bzgl. Laufzeitverhalten oder Speicherbedarf gibt, wird die Aufgabe sehr viel komplizierter.
Ein Compiler
erzeugt aus einem Quellprogramm , das in einer
Quellsprache geschrieben ist, ein Zielprogramm in
einer Zielsprache . Statt Zielprogramm findet man auch
oft die Bezeichnung Objektprogramm .
Der Compiler ist in einer Implementierungssprache
geschrieben.
Graphisch kann man einen Compiler durch ein T-Diagramm beschreiben:
Das erzeugte Programm wird dann auf die Zielmaschine geladen und ausgeführt. Dabei findet die Interpretation der Befehle direkt durch die Hardware/das Betriebssystem der Zielmaschine statt.
Ein Interpreter
ist ein Programm, das auf der Zielmaschine ausgeführt wird, als
Eingabe - wie der Compiler - ein Quellprogramm erwartet und dieses
Programm direkt interpretiert und unmittelbar ausführt.
Vorteile eines Interpretierers:
Nachteile eines Interpretierers:
Geschwindigkeit des ausgeführten Programms
Man sieht, daß sich diese Ziele zum Teil widersprechen:
Effizienz
kompakter Code
gute Fehlerdiagnose
kleiner Compiler
kurze Übersetzungszeit
Effizienz
Angenommen auf einer SUN-Workstation existiert ein C-Compiler. Wir
möchten einen C-Compiler (1) für eine Intel-Maschine in C schreiben. Dann
gehen wir wie folgt vor:
Im ersten Schritt erzeugen wir mit dem SUN-Compiler (0) einen
C-Compiler (2), der auf SUN läuft und 586-Code erzeugt. Damit
übersetzen wir (1) noch einmal und erhalten (3), einen C-Compiler
der auf Intel läuft.
Ein guter Test ist nun das folgende Vorgehen:
(4) ist ein Compiler, der auf Intel von einem auf SUN generierten
Compiler erzeugt wurde.
Wir stecken (1) noch einmal in (4) und erhalten (5):
Wenn (4) und (5) identisch sind, haben wir ein gutes Indiz, daß unser Compiler korrekt arbeitet.
Will man n Quellsprachen für m Maschinen übersetzen, so benötigt
man prinzipiell n*m Compiler. Die Verwendung einer gemeinsamen
Zwischensprache erlaubt die Reduktion auf nur n+m Programme.
Ein weiterer Vorteil ist die einfache Mischbarkeit verschiedener
Sprachen:
Prof. Dr. Reinhard Völler