titlebox-logo
Für viele Programmiersprachen gibt es sogenannte Compiler. Diese Tools wandeln den in einem Texteditor geschriebenen Quellcode einer bestimmten Programmiersprache in den Maschinencode um, sodass dieser direkt vom Computer ausgeführt werden kann. Beim Kompilieren des Quellcodes überprüft der Compiler gleichzeitig auch die Logik der vom Programmierer eingegebenen Befehle.

    Inhaltsverzeichnis:

Lesezeit: 12 Min
Teile den Artikel

Wie funktioniert ein Compiler?

Moderne Compiler übernehmen vielfältige Aufgaben, bevor der eigentliche Maschinencode generiert wird. Hierzu zählen:

  • die Syntaxprüfung
  • die Analyse und ggf. Optimierung
  • die Codeerzeugung bzw. das Linken

Zunächst wird beim Quellcode einer Programmiersprache geprüft, ob es sich hierbei um ein gültiges Programm handelt. Wichtig ist, dass der Code der Syntax der verwendeten Zielsprache entspricht. So können beispielsweise BASIC-Befehle nicht mit C- oder Fortran-Befehlen gemischt werden. Mögliche Fehler werden vom Compiler protokolliert und an der betreffenden Stelle im Code angezeigt. Durchweg erfolgt danach eine Zwischendarstellung des vom Programmierer geschriebenen Codes auf dem Bildschirm.

Die Zwischendarstellung wird danach analysiert und auf Wunsch optimiert. Bei einigen Compilern lässt sich die Optimierung individuell anpassen. Neben einer einfachen Effizienzoptimierung reicht diese bis zu einer vollständigen Programmanalyse, was jedoch einen höheren Zeitaufwand bedeutet.

Anschließend erfolgt die Codeerzeugung. Hierbei wird die vorgenannte Zwischendarstellung in den eigentlichen Zielcode übersetzt. Interessant ist, dass viele aktuelle Compiler keine Codeerzeugung selbst vornehmen. Bei der Programmiersprache C++ erfolgt die Generierung bei aktivierter globaler Optimierung erst beim Linken.

Beim Linken greift der Compiler auf weitere Laufzeitbibliotheken zurück, in der bereits zahlreiche Funktionen in Maschinencode übersetzt worden sind. Diese Bibliotheksfunktionen werden dann mit dem zu erstellenden Code verbunden, sprich verlinkt.

Die modernen Programmiersprachen arbeiten oft mit einem Bytecode statt mit einem Maschinencode. Ein Bytecode ist eine Art Pseudocode für Maschinen, der dann in speziellen virtuellen Maschinen ausgeführt wird.

C# und Java erstellen zunächst einen Zwischencode, der während der Laufzeit kompiliert wird. Vorteilhaft bei einer solchen Runtime-Codegenerierung sind:

  1. eine exaktere Anpassung an die gewünschte Zielplattform
  2. modulübergreifende Code-Optimierungen
  3. Nutzung von weiteren individuellen Profil-Informationen.

Aufgabengebiet eines Compilers

Compiler dienen als Übersetzer und sind für viele Programmiersprachen verfügbar. Grundsätzlich ist es für Menschen recht schwierig, den Programmcode eines Computers, bestehend aus Nullen und Einsen, zu verstehen und zu überblicken. Schon 1949 entwickelte daher die Amerikanerin und Mathematikerin Grace Hopper eine Methode, mit der Programmcode in einer für Menschen verständlichen Befehlsform dargestellt werden konnte. Heute ist bekannt, dass jede Programmiersprache über eine eigene Syntax verfügt, die verhältnismäßig einfach zu verstehen und nachvollziehbar ist. Damit die Syntax einer Programmiersprache übersetzt werden konnte, wurden früher eigenständige Compiler und Linker verwendet.

Wer heute den Quellcode einer Programmiersprache in ein ablauffähiges Computerprogramm übersetzen möchte, der muss nach der Programmierung lediglich auf den Button Kompilieren drücken und erhält im Ergebnis bei Windows-Systemen eine ausführbare EXE-Programmdatei. Das Aufgabengebiet eines Compilers fällt somit eindeutig in den Bereich der Programmierung und ist hier ein mittlerweile integriertes Tool.

Eine weitere Aufgabe dient der Programmoptimierung, auch wenn diese mittlerweile während der Codeerstellung von der CPU übernommen wird. Unabhängig davon lassen sich zusätzliche Optionen vor einer Kompilierung einstellen. Hauptziel sind ein besseres Laufzeitverhalten und eine Speicherplatzoptimierung. Da Arbeitsspeicher in heutiger Zeit relativ preisgünstig ist und ausreichend zur Verfügung steht, wird auf diesen Optimierungsschritt leider weitgehend verzichtet. Die wichtigsten Aufgaben und Optimierungen sollen nachfolgend vorgestellt werden.

Einsparen von überflüssigen Maschinenbefehlen

Tatsächlich gibt es im Rahmen der Programmierung zahlreiche Variablen und Befehle, die doppelt oder gar überflüssig ausgeführt werden. Ein Beispiel stellen die sogenannten Hilfsvariablen dar, in denen vorübergehende Werte zwischengespeichert werden. Diese sind nicht immer erforderlich. Ein Vertauschen von Werten kann beispielsweise wesentlich schneller über freie Register innerhalb des Prozessors durchgeführt werden, als über eine zusätzliche Variable, die im Arbeitsspeicher bereitgehalten werden muss.

Von daher sorgt das Ablegen der Daten in den Registern für eine praktische Codeoptimierung. Mit leistungsstarken Prozessoren lassen sich Assemblerbefehle, also der Maschinencode, noch effizienter ausführen. Im Optionen-Menü des Compilers kann diesbezüglich eingestellt werden, ob die Übersetzung für einen solchen Hochleistungsprozessor vorgenommen werden kann. Der Code wird dadurch etwas schlanker.

Formeln während der Übersetzungszeit auswerten

Eine statische Formel-Auswertung zur Übersetzungszeit wird als Konstantenfaltung bezeichnet.

Wenn mehrere Formeln mit Platzhaltern bzw. Konstanten zu berechnen sind, lassen sich viele Zwischenspeicherschritte einsparen. Beispiel:

Zahl_Pi = 3.14159

Umfang = 2 * Zahl_Pi * Radius

Vereinfacht:

Umfang = 6.28318 * Radius

Hierbei wird die Formel 2 * Zahl_Pi direkt eingespart und durch das Ergebnis ersetzt. Somit lässt sich Rechenzeit einsparen. Gute Compiler erkennen diese Code-Optimierung.

Erkennen und löschen von unbenutztem Programmcode

Es kann durchaus im Eifer des Gefechts vorkommen, dass Programmierer Programmroutinen schreiben, die eigentlich nie im Programm ansprechen oder durchlaufen werden. Es handelt sich hierbei um Dead Code oder toten Programmcode. Ein guter Compiler erkennt diesen unnützen Code und lässt diesen bei der Übersetzung in Maschinensprache einfach weg.

Unbenutzte Variablen eliminieren

Ebenso werden vielleicht zahlreiche Variablen mit bestimmten Werten besetzt, die nicht benötigt werden. Bei der Codeerstellung lässt sich diese unnötige Speicherplatzreservierung beseitigen. Auch hier kann der Compiler einen effizienteren Programmcode erzeugen.

Schleifenoptimierung

Große Bedeutung haben Programmschleifen. Diese sollten dahingehend überprüft werden, ob innerhalb der Schleifen Optimierungsbedarf besteht. Ein Beispiel stellen Berechnungen innerhalb des Schleifenkörpers dar, die immer dasselbe Ergebnis liefern. Diese Berechnung wird dann außerhalb der Schleife nur einmal vorgenommen.

Darüber hinaus werden zwei gleiche Schleifen eliminiert, wenn diese dasselbe Ergebnis liefern. Es kann auch durchaus vorkommen, dass eine Schleife nach einer Optimierung nur noch über einen leeren Rumpf verfügt. In diesem Fall löscht der Compiler die gesamte Schleife.

Integration von Unterprogrammen

Wegen einer besseren Übersicht erstellen viele Programmierer kleinere Unterprogramme im Quelltext. Je nach Compiler ist es einfacher, gleich den Programmcode in das Hauptprogramm direkt einzufügen, als hier extra ein Unterprogramm aufzurufen. Diese Integration wird auch als Inlining bezeichnet. Einige Programmiersprachen besitzen Inline-Schlüsselwörter, die den Compiler speziell darauf hinweisen. Dieser weiß dann genau, welche Bereiche optimiert werden sollen.

Unterschiede zwischen Compiler und Interpreter

Während ein Compiler einen Quellcode einer Programmiersprache vollständig einliest und eine Übersetzung in den Maschinencode vornimmt, gibt es noch die Möglichkeit, die Syntax einer Programmiersprache zeilenweise auszuführen.

Diese Aufgabe übernimmt ein sogenannter Interpreter.

Die Interpreter sind besondere Programme, welche einen eingegebenen Text in einer Programmiersprache Zeile für Zeile nacheinander abarbeiten und dabei direkt in Maschinencode übersetzen. Wichtig zu wissen ist, dass hierbei kein ausführbarer Programmcode erzeugt wird. Wer ein selbst geschriebenes Computerprogramm in Interpretersprache schreibt, muss zum Ausführen immer den Interpreter starten. Die Übersetzung der Quelldatei in Maschinensprache erfolgt dabei immer zur Laufzeit des Programms. Dementsprechend erfolgt die Entwicklung deutlich einfacher bei Anwendung eines Interpreter, als bei der Anwendung eines Compilers.

Zu den bekanntesten Interpretersprachen gehören BASIC, JavaScript und VBA. Interpreter arbeiten wesentlich langsamer als Compiler. Der große Vorteil besteht jedoch darin, dass durch die zeilenweise Übersetzung bei einem auftretenden Fehler genau die Stelle bezeichnet werden kann, an welcher der Fehler aufgetreten ist. Interessant ist beispielsweise der legendäre GW-BASIC-Interpreter, bei dem jede Programmzeile durchnummeriert war. Die Zeilennummern waren notwendig, wenn interne Programmsprünge ausgeführt werden sollten.

Interpreter eignen sich hervorragend zu Schulungszwecken. Mit geteiltem Bildschirm lässt sich sehr gut die zeilenweise Abarbeitung wiedergeben. Während in einem Fenster das eigentliche Programm abläuft, zeigt der Interpreter in einem anderen Fenster die schrittweise Programmausführung an. Hierbei lässt sich sehr gut darstellen, wie Sprungbefehle ausgeführt werden.

Ein weiteres Beispiel stellt die Programmiersprache COBOL dar. Sie repräsentiert eine zwar alte, aber immer noch gebräuchliche Datenbanksprache. Diese ist zunächst als Interpretersprache erhältlich. Der Compiler muss separat erworben werden, so dass aus dem Quellcode ein eigenständig lauffähiges Programm übersetzt werden kann. COBOL Compiler sind auch heute noch gebräuchlich, zumal diese Sprache mittlerweile auch objektorientiert gestaltet ist.

Beispiel für die Vorgehensweise mit einem Compiler

Das nachfolgende Beispiel soll die Arbeitsweise mit dem legendären Turbo-Pascal-Compiler verdeutlichen.

Turbo Pascal in der Version 3.0 bot noch keine integrierte Entwicklungsumgebung. Das unter MS-DOS arbeitende Programm präsentiert sich mit folgendem Bildschirminhalt:

Logged drive: C

Work file:

Main file:

Edit Compile Run Save

eXecute Dir Quit compiler Options

Text: 0 bytes

Free: 62932 bytes

Über Edit wurde der Texteditor zur Eingabe des Quellcodes aufgerufen. Es handelte sich hier um eine stark vereinfachte Textverarbeitung. Das eigentliche Programm wurde in der Pascal-Syntax eingegeben.

Für das allseits bekannte HalloWelt-Programm sähe es folgendermaßen aus:

program HalloWelt;

begin

WriteLn(‘Hallo Welt!’);

ReadLn;

end.

Dieses Programm kann über den Befehl Run sofort mithilfe des integrierten Interpreters ausgeführt werden. Gleichzeitig wurden mögliche Programmfehler sofort angezeigt. Das Programm zeigte den Schriftzug Hallo Welt! auf dem Bildschirm an. Damit die Anzeige nicht sofort nach Programmbeendigung wieder verschwand, wartete das darunter befindliche ReadLn auf einen Tastendruck des Nutzers. Erst danach wurde das Programm beendet.

Über den Menübefehl Compile bestand die Möglichkeit, dieses einfache Programm in eine ablauffähige COM-Datei zu übersetzen. Der Compiler muss jedoch separat aufgerufen werden.

Spätere Versionen von Turbo Pascal besaßen eine richtige Entwicklungsumgebung mit noch mehr Funktionen. Auch hier ließen sich die Programme bequem kompilieren. Der Compiler ist über einen speziellen Menüpunkt erreichbar. Da die erzeugten COM-Dateien größenmäßig begrenzt waren, ließen sich hier erstmals auch EXE-Dateien erstellen.

Beispiel für die Vorgehensweise mit einem Interpreter

Microsoft hat für sein MS-DOS-Betriebssystem einen einfachen BASIC-Interpreter mit Namen GW-BASIC mitgeliefert.

Eine Entwicklungsumgebung fehlte vollständig, sodass dieses zeilenorientierte Programm lediglich über die Kommandozeile bedient wurde. Mit dem Befehl New wurde der gesamte Programmspeicher gelöscht, mit Load ließ sich ein abgespeichertes Programm einlesen und mit Save speichern. Mit dem Befehl System wird GW-BASIC beendet.

Zur Programmeingabe wurde ein einfacher Editor gestartet. Interessant ist, dass jede Zeile über eine eigene Zeilennummer verfügen muss. Dies verdeutlicht das folgende Beispiel:

10 CLS

20 PRINT“Wie heißen Sie?“

30 INPUT NAME$

40 PRINT“Sie heißen “;NAME$;“.“

50 END

Mit dem Befehl Run wird das Programm in einem Interpreter gestartet. Dabei arbeitet der Interpreter Zeile für Zeile ab und stoppt bei einem möglichen Fehler. Mit dem Befehl CLS wird der gesamte Bildschirminhalt gelöscht. Nach der Frage „Wie heißen Sie?“ wartet das Programm auf eine Eingabe. Hier lassen sich beliebige Buchstaben und Zahlen eingeben und mit Enter bestätigen. Die Angabe der Zahlen vor jeder Zeile ist für den Interpreter wichtig, da dieser genau weiß, welchen Befehl er als nächstes abarbeiten muss. Die Zahlenabstände sind jedoch willkürlich. Diese können auch eine Folge von 11, 12, 13,14 etc. aufweisen.

Just-In-Time-Compiler: Eine Mischung aus Compiler und Interpreter

Noch relativ neu sind die sogenannten Just-In-Time-Compiler, kurz JIT-Compiler genannt.

Dabei steht die Ausführungsgeschwindigkeit eines Interpreters im Vordergrund. In heutiger Zeit werden zur Programmerstellung viele verschiedene Programmiersprachen eingesetzt. Diese sollen sowohl auf Desktop-PCs, auf Tablets und auch auf Smartphones laufen.

Um eine solche Plattformunabhängigkeit am besten testen zu können, werden diese Programme zunächst in einer virtuellen Maschine ausgeführt. Dabei simuliert beispielsweise ein leistungsstarker PC einen anderen PC, eine besondere Rechnerkonstellation oder ein mobiles Endgerät.

Es gibt zahlreiche Programmiersprachen, welche keinen Maschinencode vor ihrer Ausführung kompilieren. Damit das Programm aber dennoch bei Bedarf lauffähig ist, erfolgt eine JIT-Kompilierung. Anzumerken ist, dass der Programmablauf auf einer virtuellen Maschine in etwa mit der Geschwindigkeit eines normalen Interpreters vergleichbar ist. Mithilfe einer teilweisen Kompilierung des Codes zur Laufzeit kann ein JIT-Compiler das Programm wesentlich schneller ausführen.

Ein JIT-Compiler erzeugt folglich zur Laufzeit des Programms einen Maschinencode. Da die Kompilierung während der Programmausführung durchgeführt wird, kann es sich nicht um eine aufwändige Codeumwandlung handeln, da sich ansonsten die Arbeitsgeschwindigkeit stark verringern würde.

Aus diesem Grund wandeln JIT-Compiler in aller Regel nur solche Programmteile um, die häufig ausgeführt werden. Hierzu gehören beispielsweise besondere Unterprogramme, Schleifen oder andere wiederkehrende Teile. Diese nehmen den größten Teil der Rechenleistung ein, sodass sich eine Kompilierung während der Laufzeit lohnt.

Eine wichtige Aufgabe des JIT-Compilers ist es, häufig genutzte Programmteile aus dem Quellcode zu identifizieren, zu analysieren, ggf. zu optimieren und in Maschinensprache zu übersetzen. Je nach Qualität des JIT-Compilers werden die übersetzten Programmteile in einer Zwischenablage zwischengespeichert, um notfalls mehrmals darauf zugreifen zu können. Auch hier muss wieder der Geschwindigkeitsvorteil erwähnt werden.

Häufig werden während der Software Entwicklung bei den sogenannten Ahead-of-time-Compilern nicht alle sinnvollen Optimierungen bei der abschließenden Übersetzung verwendet. Wenn der Programmierer keine automatischen Optimierungen anwendet, können Tests und Anwendungsszenarien durchgespielt werden, so kann herausgefunden werden, an welcher Stelle sich komplexe Optimierungen lohnen.

Bedeutung von Compilern für die Suchmaschinenoptimierung

Viele Web-Browser sind in der Lage, den Quellcode einer Internetseite anzuzeigen.

Auf diese Weise lässt sich sehr gut die Funktionsweise nachvollziehen. Interessant ist dies beispielsweise, wenn ein Direktlink zu eingebetteten Bildern gefunden werden soll. Mit folgenden Browsern lässt sich der Web-Code anzeigen:

  • Google Chrome
  • Internet Explorer
  • Microsoft Edge
  • Mozilla Firefox

Die vorgenannten Browser verfügen über einen Menüpunkt, mit dem sich der Seitentext darstellen lässt.

Mit einem Compiler ist es möglich, den Quellcode einer Webseite genauer zu untersuchen. So lassen sich hierüber versteckte Schlüsselbegriffe suchen. Zudem wird die Fehlersuche für Webprogrammierer einfacher. Viele Analysetools sind mit integrierten Compilern ausgestattet. Auf diese Weise lassen sich die Webseiten noch genauer unter die Lupe nehmen. Besonders wichtig ist eine Codekomprimierung, mit der die Ladezeiten der Webseiten nochmals verringert werden können.

Viele bekannte Hersteller bieten solche Analyse-Compiler-Tools an, die zur Suchmaschinenoptimierung eingesetzt werden können.

Fazit

Mit einem Compiler können Programmierer bzw. Entwickler aus einem Quellcode einer Programmiersprache selbstständig lauffähige Programme erstellen. Viele Programmiersprachen verfügen in ihrer Entwicklungsumgebung über geeignete Techniken zum Kompilieren, mit denen eine Übersetzung in den Maschinencode möglich ist. Eine Alternative bieten die Interpreter, bei denen der Code in Echtzeit während der Programmausführung in die Maschinensprache übersetzt wird.

Interpreter arbeiten insgesamt langsamer, bieten aber den Vorteil, dass Fehler schneller erkannt werden. Nicht unerwähnt bleiben sollen die Analysetools für Webseiten, die ebenfalls auf eine Compiler-Technologie vertrauen. Damit können Webseiten auch für Suchmaschinen optimiert werden. Compiler spielen somit bei der Programmierung, als auch bei der Suchmaschinenoptimierung, durch Analyse von Webseiten, eine wichtige Rolle.

 

omt logo

Diesen Artikel jetzt als Podcast anhören

Jetzt anhören auf: Spotify | Apple Podcast | Google Podcast

Teile den Artikel
Wie ist Deine Meinung zu dem Thema? Wir freuen uns über Deinen