Inhaltsverzeichnis

Wie unterscheidet man Übersetzer?

Die Überschrift vermeidet das Wort 'Compiler', da es mir im deutschen Sprachgebrauch zu sehr auf Erzeugung von ausführbaren Dateien festgelegt ist. Für die Unterscheidung von Übersetzern spielen jedoch mehrere Faktoren eine Rolle, die sich in der Bearbeitung des Quellcodes bis zum wirklich laufenden Programm darstellt.

Übersicht

Die Unterteilung Interpreter und Compiler ist nahezu nichtssagend, da sie viel zu ungenau ist und die Verwendung der Worte extrem schwammig und sehr häufig auch schlichtweg falsch ist.

Allgemein wird das Wort Interpreter verwendet, wenn ein Programm gemeint ist, das Quelltext sofort umsetzt. Ein Compiler hingegen übersetzt den Quelltext, führt ihn nicht aus, sondern speichert das Compilerprodukt auf der Festplatte ab. Doch auch diese Unterscheidung ist vollkommen unzureichend.

Der Interpreter ist „ein Programm, welches ein Programm einer anderen Programmiersprache nach den notwendigen syntaktischen Überprüfungen sofort ausführt“, schreibt der Informatik-Duden[13, S. 311] dazu und beschreibt den Compiler [13, S.684] als ein „Programm, das Programme aus einer Programmiersprache A in eine Programmiersprache B übersetzt“. Die Unterscheidung trifft der Informatik-Duden[13, S.311] durch den Moment, in dem eine Anweisung ausgeführt wird: „im Gegensatz zu einem Übersetzer muss das Quellprogramm nicht erst in eine andere Programmiersprache übersetzt werden, sondern der Interpreter analysiert nacheinander jede Anweisung und Deklaration des Quellprogramms und führt diese unmittelbar aus.“.

So findet sich in jedem Compiler ein Interpreter, welcher auf den Namen Parser umgetauft wurde und den Parserbaum erstellt. Er interpretiert den Quelltext und erstellt daraus nach den notwendigen syntaktischen Überprüfungen den Parserbaum. Er führt also das Programm sofort in einer Form aus, in der die Anweisung „if“ beispielsweise bedeutet, einen Knoten des Typs „Verzweigung“ im Parserbaum anzulegen.
Die Funktion des Parsers passt allerdings auch auf die Beschreibung des Informatikdudens, um als Compiler bezeichnet zu werden. Die Programmiersprache A wird in eine Programmiersprache B, in diesem Fall den Parserbaum, übersetzt.
Das Drachenbuch, als Standardwerk des Compilerbaus, liefert eine ähnliche Beschreibung zum Compiler: „Im Grunde ist ein Compiler ein Programm, das ein in einer bestimmten Sprache – der Quellsprache – geschriebenes Programm liest und es in ein äquivalentes Programm einer anderen Sprache – der Zielsprache – übersetzt.“

Kurzum: Standardwerk und Informatik-Lexikon halten sich mit einer scharfen Trennung zurück, beides reicht nicht, um zum Beispiel Perl als Interpreter oder als Compiler einzustufen.

Interpreter und Compiler treten in der Regel in verschiedenen Formen gemeinsam auf. Es stellt sich also vielmehr die Frage, wann wird interpretiert, wann wird kompiliert und an welchen Punkten kann der Entwickler Einfluss nehmen.

Reine Interpreter

Da jeder Compiler, selbst Assembler, mit dem Parser einen Interpreter integriert hat, kann es keine reinen Compiler geben. Ein Interpreter kann jedoch ohne Compiler auskommen. Erste lauffähige Interpreter fanden sich bereits im 19. Jahrhundert. Sie interpretierten ein Programm, dass sie mechanisch aus Endlos-Lochstreifen lasen und entsprechend des Programms die Mechanik der ausführenden Maschine steuerten. Es handelt sich um automatische Webstühle, wie sie zum Beispiel im Heinz-Siemens-Forum in Paderborn noch heute zu besichtigen sind.

Reine Interpreter eignen sich hervorragend, um zu schnellen Ergebnissen zu gelangen. Sie bieten in der Regel hohe Funktionalität, gepaart mit einem Textparser, der entsprechend des Quelltextes die Funktionalität des Interpreters aufruft (zum Beispiel um einen Text auf dem Bildschirm auszugeben). Interpreter sind schnell und einfach zu implementieren und dienen so bei vielen verschiedenen Beschreibungs-Sprachen, um zum Beispiel größere Anwendungen konfigurieren (z.B. XML-Parser). Dabei wird der Quelltext Wort für Wort gelesen und sofort ausgeführt, alle zeitraubenden Kompiliervorgänge liegen mit dem Interpreter bereits vorbereitet vor, so dass das Programm sofort gestartet wird. Als größter Vorteil von interpretierenden Sprachen gilt ihre Plattformunabhängigkeit. Da sich die Programme nur innerhalb der Möglichkeiten des Interpreters bewegen, kann dieser auf unterschiedliche Plattformen portiert werden, so dass alle Programme dieser Sprache unverändert auch auf einer anderen Plattform laufen.

Zur Zeit bekannte interpretierende Sprachen sind beispielsweise JavaScript, Bash oder die Mehrzahl der Basicdialekte.
Damit eigenen sich Interpretersprachen zur schnellen Entwicklung von kleineren Skripten, also kleineren Aufgaben, deren Abarbeitung schnell abgeschlossen wird.
Steuerungsprogramme, die vorrangig Interpreterfunktionen nutzen lassen sich so schnell und effizient entwickeln und starten. Programme, die jedoch größere eigene Berechnungen anstellen, bei denen sie nicht vom Interpreter unterstützt werden können, verbrauchen Ihren Vorteil durch den schnellen Start kurze Zeit später durch die langsame Ausführungsgeschwindigkeit ihrer Berechnungen.

Compiler

Der Compiler interpretiert zunächst das Quellprogramm und erstellt ein gleichbedeutendes Programm in einer anderen Darstellung.

Kompilierende, sofort ausführende Sprachen

Um das Manko der langsamen Berechnungsgeschwindigkeit zu verringern, besitzen manche interpretierenden Sprachen einen eingebauten Compiler, der jedoch nicht auf die Plattform kompiliert, sondern lediglich einen internen Zwischencode erzeugt. Dieser kann anschließend von der Maschine effizienter interpretiert werden. Zu dieser Gruppe gehört Perl, die als eine der schnellsten „interpretierten“ Sprachen gilt. Die höhere Ausführungsgeschwindigkeit wird sich hier mit zusätzlichem Aufwand zurückgekauft: der Quelltext wird in ein schneller zu interpretierendes Programm kompiliert. Da in interpretierende Sprachen jedoch zumeist kleinere Projekte verwirklicht werden, ist diese einfache Kompilierung meist schnell erledigt und das Programm fertig abgelaufen, bevor ein richtiger Compiler Maschinencode erzeugt hätte, der die interpretierende Sprache binnen Millisekunden überholt hätte.

Zwischencode speichernde Sprachen

Zu dieser Sparte zählt sich Java, dass zunächst ein Javaprogramm in einen maschinenunlesbaren Zwischenschritt kompiliert. Dieser Zwischencode (Bytecode) wird aber anschließend nicht wie bei Perl ausführt, sondern auf die Festplatte gespeichert. Damit erspart man sich beim mehrfachen Ausführen die wiederholte Kompilierung. Der Zwischencode muss anschließend wieder interpretiert werden.
Die Interpretation erfolgt entsprechend effizient, da sie in einem optimierten Format vorliegt, die Startzeiten sind kurz, da die Kompilierung ebenfalls bereits abgeschlossen ist. Durch das Laden des gigantischen Java-Interpreters und ggfs. einer Vielzahl von Packages, die zusätzlich notwendig sind, wird dieser Vorteil bei Java nicht mehr wahrgenommen und weit mehr als zunichte gemacht.

Obwohl Java keine ausführbaren Dateien erzeugt, spricht man hier umgangssprachlich von einer „kompilierenden“ Sprache. Das ist in sofern auch korrekt, da tatsächlich ein Quellprogramm in ein anderes Format überführt wird.

Die Virtual-Maschine von Java interpretiert nun den Bytecode erneut. Die modernen JavaVM nutzen dabei eine Weiterentwicklung des einfachen Interpreters, wie er im Folgenden Kapitel „Just-In-Time-Kompilierung“ beschrieben wird.

Nativ kompilierende Sprachen

Unter nativen Programmen versteht man Programme, welche in für die Zielplattform ausführbare Maschinensprache kompiliert und im für das Zielbetriebsystem passenden Dateiformat gespeichert wurden. Die direkte Kompilierung in native Programme wird von den wenigsten Programmiersprachen unterstützt und ist heutzutage eher als unüblich zu betrachten. Sie eignet sich vorrangig für Sprachen, die vorrangig für kleinere Programme verwendet werden und wird zum Beispiel von PureBasic unterstützt.

Just-In-Time Kompilierung

Just-In-Time Compiler stellen zur Zeit das Highlight im Bereich der Interpretierung dar. Statt den Code zu lesen, auszuführen und wieder zu vergessen, werden die benötigten Bereiche kompiliert und anschließend im Speicher verwahrt. Damit können sie wiederverwendet werden. Schleifen können so mit einer Kompilierung massiv beschleunigt werden. Konstante Ausdrücke, können eingefügt werden, statt sie bei jedem Schleifendurchgang zu berechnen. Überprüfungen, die der Interpreter leistet, können unter Umständen vollständig wegfallen. Als Beispiel sei die Division durch eine Konstante genannt: Diese Konstante ist entweder null oder sie ist nicht null. Im zweiten Fall wird die Überprüfung bei weiteren Schleifendurchgängen nicht mehr erforderlich. Der Compiler kann diese Prüfung also kürzen und so die Abarbeitung beschleunigen.

Durch Just-In-Time Compiler können Interpreter nahezu die gleiche Geschwindigkeit wie native Sprachen erhalten, die zu interpretierenden Eingabefiles bleiben dabei jedoch plattformunabhängig. Aber auch hier fallen zuerst wieder Vorbereitungen an, die eine rein interpretierende Sprache oder ein natives Programm nicht leisten müssen.

Zunächst begann man kurze Abschnitte in einen kleinen Cache mit nativen Programmteilen zu übersetzen, die Schleifen und kleinere Konstrukte beinhalteten. Ein kleiner Cache von wenigen Kilobytes Größe ist jedoch durch die Maschine schnell abgearbeitet, so dass der JIT-Compiler den Cache häufig mit dem nachfolgenden Code füllen musste. Durch das Nachkompilieren von Bytecode wurde das Programm an unkontrollierten Stellen unterbrochen und kurzzeitig reaktionsunfähig. Die Ausführung wird damit also ausgebremst.
Um dem zu Begegnen vergrößerte man den Cache, so dass auch nahe liegende Funktionsaufrufe vorkompiliert Platz fanden und durch geschicktes Management auch bevorzugt dort in nativer Form verblieben. Inzwischen erhält eine Ausführung häufig einen mehrere Megabyte großen Cache. Dies hat den Vorteil, dass der zu interpretierende Code häufig vollständig übersetzt werden kann. Das Programm läuft damit nativ und daher etwa in der gleichen Ausführungsgeschwindigkeit wie ein normales Executable einer nativ kompilierenden Sprache. Nachteilig wirkt sich diese Technik auf den Speicherverbrauch aus. Hier kann der Anwender nur noch durch die Anschaffung einer Speichererweiterung reagieren, wenn der Rechner beginnt Teile des Programms auf die Festplatte auszulagern und somit extrem viel langsamer zur Ausführung kommt.

Objektfiles und Linker

Die meisten Compiler erzeugen heutzutage so genannte Objektfiles, die anschließend von einem Linker „gegen“ andere Bibliotheken gelinkt werden. Alle benötigten Funktionen werden so in eine Datei gebunden, so dass wieder ein natives Programm entsteht, das ohne Startzeitverzögerung laufen kann. Diese Technik wird heute von allen üblichen C, C++, Pascal, … Compilern verwendet. Ein Programm, das gegen alle benötigten Libraries gelinkt wurde, ist autonom lauffähig und benötigt zum Start keine weiteren Dateien.
Alle Sprachen, die in das übliche Objektfile (je nach System, siehe [10]) kompilieren, können zu einem nativen Programm verknüpft werden, so dass ein Programm aus Quelltexten verschiedener Sprachen bestehen darf.

Dynamisches Linken

Statt die kompilierten Bibliotheken wie früher üblich direkt mit dem Programm zusammen zu linken, wird heute dymanisches Linken häufig vorgezogen. Das bewirkt, dass ausführbare Programme kleiner werden, allerdings nicht mehr sofort starten können. Sie müssen zunächst fehlende Bibliotheken von der Festplatte nachladen. Das Nachladen geschieht hierbei nicht „dynamisch“ vom Entwickler gesteuert, sondern das Ladeprogramm des Betriebsystems blendet die zusätzlich benötigten Bibleotheken in den Adressraum des zu startenden Programms ein.
Sind die erforderlichen Abhängigkeiten nicht erfüllt, kann das Programm vom Betriebsystem nicht geladen werden. Dadurch sind sie auch nicht mehr ohne Lader (Betriebsystem) lauffähig. Sind alle Abhängigkeiten allerdings bereits vom Betriebsystem geladen, verläuft der Programmstart sehr schnell, das Programm läuft nativ und damit in maximaler Geschwindigkeit ab, und benötigt minimale Speicherresourcen.

umgangssprachliche Klassifizierung

Die umgangssprachliche Klassifizierung Interpreter bzw. Compiler läuft darauf hinaus, ob das an den Übersetzer übergebene Programm sofort ausgeführt wird oder zunächst auf die Festplatte gespeichert. Wird es sofort ausgeführt, dann handelt es sich umgangssprachlich um einen „Interpreter“ - unabhängig davon, ob der Quelltext zunächst in eine andere Sprache überführt wird. Entsteht als Ergebnis des Übersetzers eine wie Datei, die die gewünschte Repräsentation des Quellcodes enthält, spricht man umgangssprachlich von einem Compiler.