Skip to content

Einführung in die Compiler-Theorie

Vorwort

Was passiert, wenn Sie auf „Ausführen" klicken — wie wird Code zu einem Ergebnis auf dem Bildschirm? Jede Zeile Code, die Sie schreiben, kann der Computer eigentlich nicht „lesen" — er kennt nur 0 und 1. Der Compiler ist der „Dolmetscher", der menschliche Sprache in Maschinensprache übersetzt. Wenn Sie die Compiler-Theorie verstehen, wissen Sie, woher Fehlermeldungen kommen, warum manche Sprachen schnell und andere langsam sind und wie Code-Optimierung auf unterster Ebene funktioniert.

Was werden Sie in diesem Artikel lernen?

Nach Abschluss dieses Kapitels werden Sie Folgendes gewonnen haben:

  • Gesamtüberblick: Die vollständige Compiler-Pipeline vom Quellcode zum ausführbaren Programm verstehen
  • Lexikalische Analyse: Verstehen, wie der Compiler Code in einzelne Token zerlegt
  • Syntaxanalyse: Den Aufbau des AST (Abstract Syntax Tree) verstehen
  • AST-Visualisierung: Die Baumstruktur von Code direkt sehen
  • Semantische Analyse und Optimierung: Die Prinzipien der Typprüfung und Code-Optimierung verstehen
  • Optimierungstechniken in der Praxis: Constant Folding, Dead Code Elimination und weitere Kernoptimierungen beherrschen
  • Ausführungsmodelle: Kompilierte, interpretierte und JIT-Ausführung unterscheiden
KapitelInhaltKernkonzepte
Kapitel 1Was ist ein CompilerDolmetscher-Analogie, Compiler-Pipeline
Kapitel 2Lexikalische AnalyseToken, lexikalische Regeln
Kapitel 3SyntaxanalyseAST, Syntaxbaum, Prioritäten
Kapitel 4AST-VisualisierungInteraktiver Syntaxbaum, Knotentypen
Kapitel 5Semantische Analyse und OptimierungTypprüfung, Constant Folding, Dead Code Elimination
Kapitel 6Optimierungstechniken in der PraxisFunction Inlining, Loop Hoisting, Constant Propagation
Kapitel 7Kompiliert vs. Interpretiert vs. JITVergleich der drei Ausführungsmodelle

0. Überblick: Die „Reise der Übersetzung" des Codes

Stellen Sie sich vor, Sie sind ein Dolmetscher, der einen chinesischen Roman ins Englische übersetzt. Sie übersetzen nicht Wort für Wort, sondern:

  1. Wörter erkennen — den Satz in einzelne Wörter zerlegen (lexikalische Analyse)
  2. Syntax verstehen — beurteilen, ob die Satzstruktur korrekt ist (Syntaxanalyse)
  3. Semantik verstehen — sicherstellen, dass der Sinn flüssig und widerspruchsfrei ist (semantische Analyse)
  4. Verfeinern — die Übersetzung natürlicher und flüssiger machen (Code-Optimierung)
  5. Übersetzung ausgeben — die finale englische Version verfassen (Code-Generierung)

Genau das macht ein Compiler — nur dass er Programmiersprachen übersetzt.

Compiler Principles: The Art of TranslationHow code becomes machine instructions
A compiler is like a translator, turning human-readable code into machine-readable instructions
The Complete Code Translation Pipeline
1
Lexical analysis
Break code into individual words called tokens
int age = 25 → [int, age, =, 25]
2
Syntax analysis
Check grammar rules and build a syntax tree
Validate whether statement structure is correct
3
Semantic analysis
Check whether the meaning of the code is valid
Check variable definitions and type compatibility
4
Intermediate code generation
Generate a machine-independent intermediate representation
Generate bytecode or intermediate representation
5
Optimization
Improve code so it runs more efficiently
Constant folding and dead-code elimination
6
Target code generation
Generate machine code or target code
Generate x86 or ARM machine instructions
Lexical analysis: tokenization
int age = 25;
Keywordint
Identifierage
Operator=
Number25
Separator;
Syntax analysis: build a tree
Assignment statement
Variableage
Operator=
Number25
Compilation vs Interpretation
Compiled languages
Source code → Compiler → Machine code
C, Go, Rust
✓ Fast execution
✓ Compile once, run many times
✗ Slow compile step
Interpreted languages
Source code → Interpreter → Line-by-line execution
Python, JavaScript, PHP
✓ Fast development
✓ Cross-platform
✗ Slower execution
Compiler Optimization
Before:
x = 5 + 3 + 2
⬇️
After:
x = 10
The compiler can optimize code automatically and improve runtime efficiency

1. Die sechsstufige Pipeline des Compilers

Die Arbeit des Compilers kann in sechs Phasen unterteilt werden, die wie eine Fabrikpipeline nacheinander durchlaufen werden.

How a Compiler WorksA six-step journey from source code to machine code
1
Lexical analysis→ Token stream
2
Syntax analysis→ AST syntax tree
3
Semantic analysis→ Typed AST
4
Intermediate code generation→ IR (intermediate representation)
5
Code optimization→ Optimized IR
6
Target code generation→ Machine code
1Lexical analysisOutput: Token stream
Split source code into individual words called tokens, like recognizing each word in a sentence.
Recognize keywordsRecognize identifiersRecognize numbersRecognize operatorsFilter whitespace
int x = 10 + 5;
→ [int] [x] [=] [10] [+] [5] [;]
    keyword identifier operator number operator number separator
Live lexical analysis
intKeyword
xIdentifier
=Operator
10Number
+Operator
5Number
;Separator
Three Execution Models Compared
Compiled
Source Compiler Machine code CPU execution
Fast executionMust wait for compilation
C, C++, Rust, Go
Interpreted
Source Interpreter Line-by-line execution
Run immediately while writingSlower execution
Python, Ruby, PHP
JIT
Source Bytecode JIT hot path compilation Execution
Balances performance and flexibilitySlower startup
Java, JavaScript (V8)
Core idea:A compiler is like a translator: it gradually turns human-readable code into instructions the machine can run. The six stages each do one job: identify words → understand syntax → check meaning → generate IR → optimize → generate machine code.

Compiler-Pipeline

  1. Lexikalische Analyse (Lexical Analysis): Den Quellcode in einzelne Token (Wörter) zerlegen
  2. Syntaxanalyse (Syntax Analysis): Die Token zu einem Syntaxbaum (AST) organisieren
  3. Semantische Analyse (Semantic Analysis): Prüfen, ob Typen korrekt sind und Variablen deklariert wurden
  4. Zwischencode-Generierung (IR Generation): Plattformunabhängige Zwischendarstellung erzeugen
  5. Code-Optimierung (Optimization): Den Zwischencode effizienter machen
  6. Code-Generierung (Code Generation): Maschinencode für die Zielplattform erzeugen
PhaseEingabeAusgabeAnalogie
Lexikalische AnalyseQuellcode-ZeichenstromToken-StromEinen Satz in Wörter zerlegen
SyntaxanalyseToken-StromAST (Syntaxbaum)Satzstruktur analysieren
Semantische AnalyseASTTypisierter ASTPrüfen, ob der Sinn stimmig ist
ZwischencodeTypisierter ASTIRErsten Entwurf schreiben
Code-OptimierungIROptimiertes IRVerfeinern und Streichen
Code-GenerierungOptimiertes IRMaschinencodeEndfassung ausgeben

2. Lexikalische Analyse: Code in „Wörter" zerlegen

Die lexikalische Analyse ist der erste Schritt der Kompilierung. Der Compiler scannt jedes Zeichen des Quellcodes von links nach rechts und fasst sie zu sinnvollen Token (lexikalische Einheiten) zusammen.

🔤 Lexer: Split Code into Tokens

Enter a line of code and see lexical analysis results in real time

Wie das Gehirn beim Lesen eines englischen Satzes automatisch Buchstaben zu Wörtern zusammenfasst, so fasst der lexikalische Analysator Zeichen zu Token zusammen:

Quellcode: let x = 10 + 5;

Token-Strom:
[let]   → Schlüsselwort (reserviertes Wort der Sprache)
[x]     → Bezeichner (Variablenname)
[=]     → Operator (Zuweisung)
[10]    → Zahlenliteral
[+]     → Operator (Addition)
[5]     → Zahlenliteral
[;]     → Trennzeichen (Anweisungsende)

Die fünf Token-Typen

  • Schlüsselwörter: Von der Sprache reservierte Sonderwörter, z. B. let, if, return, function
  • Bezeichner: Vom Programmierer vergebene Namen, z. B. Variablen-, Funktionsnamen
  • Literale: Direkt im Code geschriebene Werte, z. B. die Zahl 42, der String "hello"
  • Operatoren: Symbole für Berechnungen, z. B. +, -, =, ===
  • Trennzeichen: Symbole zur Strukturierung des Codes, z. B. ;, ,, (, )

3. Syntaxanalyse: Den Syntaxbaum (AST) aufbauen

Die lexikalische Analyse hat den Code in Token zerlegt, aber Token sind nur isolierte „Wörter". Die Aufgabe der Syntaxanalyse ist es, diese Token nach Grammatikregeln zu einem Abstract Syntax Tree (AST) zu organisieren — er spiegelt die Struktur und Operatorpriorität des Codes wider.

Ausdruck: 1 + 2 * 3

Syntaxbaum:        Warum so?
       +       Weil * eine höhere
      / \      Priorität als + hat,
     1   *     wird 2 * 3 zuerst
        / \    als Teilbaum
       2   3   zusammengefasst

Die Bedeutung des AST

Der AST ist die „Kerndatenstruktur" des Compilers; die nachfolgende semantische Analyse, Optimierung und Code-Generierung basieren darauf. Auch moderne Entwicklungswerkzeuge nutzen den AST intensiv:

  • ESLint: Code in AST parsen und Regelverletzungen prüfen
  • Prettier: In AST parsen und neu formatiert ausgeben
  • Babel: AST parsen → transformieren → kompatiblen Code generieren
  • IDE-Refactoring: Sicheres Umbenennen von Variablen und Extrahieren von Funktionen auf AST-Basis
SyntaxstrukturToken-SequenzAST-Knoten
Variablendeklarationlet x = 10VariableDeclaration → Identifier + Literal
Funktionsaufrufadd ( 1 , 2 )CallExpression → Identifier + Arguments
Bedingte Anweisungif ( a > b )IfStatement → BinaryExpression + Block

4. AST-Visualisierung: Das „Gerüst" des Codes sehen

Oben haben wir die Struktur des AST textuell beschrieben, aber „sehen" ist intuitiver als „lesen". Die folgende interaktive Komponente ermöglicht es Ihnen, verschiedene Ausdrücke auszuwählen und deren Syntaxbaum in Echtzeit zu beobachten.

🌳 AST Visualizer: See the Skeleton of Code

Choose an expression and inspect its abstract syntax tree

Syntax tree
BinaryExpression+
NumericLiteral1
BinaryExpression*
NumericLiteral2
NumericLiteral3
Parse notes
1* has higher precedence than +, so 2 * 3 groups first
22 * 3 forms a BinaryExpression subtree
31 and that subtree become the left and right operands of +
4The final + node is the root, showing the evaluation order
💡 Try AST Explorer — inspect ASTs for arbitrary code online

Durch die Visualisierung erkennen Sie, dass die Kernregeln des AST eigentlich sehr einfach sind:

CodestrukturAST-WurzelknotenUnterknoten
1 + 2 * 3BinaryExpression (+)Links: NumericLiteral(1), Rechts: BinaryExpression(*)
let x = 10VariableDeclarationVariableDeclarator → Identifier(x) + NumericLiteral(10)
add(a, b)CallExpressionIdentifier(add) + Arguments(a, b)

AST in der täglichen Entwicklung

Sie haben vielleicht noch nie einen Compiler geschrieben, aber Sie nutzen täglich AST-basierte Werkzeuge:

  • ESLint / Prettier: Code in AST parsen, Regeln prüfen oder neu formatieren
  • Babel / SWC: AST parsen → Syntax transformieren → kompatiblen Code generieren
  • IDE-Refactoring: Sicheres Umbenennen, Funktionen extrahieren auf AST-Basis
  • Tree-shaking: Importe/Exporte im AST analysieren, ungenutzten Code entfernen

5. Semantische Analyse und Code-Optimierung

Die Syntaxanalyse stellt sicher, dass der Code „strukturell korrekt" ist, aber strukturelle Korrektheit bedeutet nicht „inhaltliche Korrektheit". Die semantische Analyse prüft, ob die Bedeutung des Codes zulässig ist, und die Code-Optimierung sorgt dafür, dass das Programm schneller läuft.

Compilation PracticeFrom code to executable file
Input code
Compilation steps
1
Preprocess
gcc -E hello.c -o hello.i
Process #include and expand macros
2
Compile
gcc -S hello.i -o hello.s
Generate assembly code
3
Assemble
gcc -c hello.s -o hello.o
Generate object file
4
Link
gcc hello.o -o hello
Generate executable file
Generated files
📄
hello.c
Source code file
📝
hello.i
Preprocessed file
⚙️
hello.s
Assembly code file
📦
hello.o
Object file
🚀
hello
Executable file
Common compiler tools
GCC
GNU Compiler Collection
Clang
LLVM C/C++ compiler
MSVC
Microsoft Visual C++

4.1 Semantische Analyse: Prüfen, ob die „Bedeutung" stimmt

PrüfgegenstandBeispielErgebnis
Typprüfungint x = "hello"❌ Typinkompatibilität
GültigkeitsbereichsprüfungVerwendung einer nicht deklarierten Variable y❌ Variable existiert nicht
Typrückschluss1 + 2.0✅ Ergebnis als float erschlossen
Parameterprüfungadd(1, 2, 3) aber Funktion akzeptiert nur 2 Parameter❌ Parameteranzahl stimmt nicht

Die meisten Fehlermeldungen stammen aus der semantischen Analyse

  • TypeError: Cannot read properties of undefined — Typprüfung
  • ReferenceError: x is not defined — Gültigkeitsbereichsprüfung
  • Expected 2 arguments, but got 3 — Parameterprüfung

4.2 Code-Optimierung: Das Programm schneller machen

Vor der Generierung des endgültigen Codes führt der Compiler verschiedene Optimierungen am Zwischencode durch. Diese Optimierungen sind für den Programmierer transparent, können aber die Performance deutlich steigern.

OptimierungstechnikVorherNachherPrinzip
Constant Foldingx = 10 + 5x = 15Ergebnis wird zur Compile-Zeit berechnet
Dead Code Eliminationif (false) { ... }Direkt entferntCode, der nie ausgeführt wird
Constant Propagationx = 15; y = x * 2y = 30Bekannte Werte direkt ersetzen
Loop Invariant Code Motionlen = arr.length wird im Loop wiederholt berechnetVor den Loop verschobenWiederholte Berechnung vermeiden

6. Optimierungstechniken in der Praxis: Wie der Compiler Code schneller macht

Oben haben wir einige Optimierungstechniken beim Namen genannt. Nun schauen wir uns an, wie der Compiler konkret vorgeht. Die folgende interaktive Komponente zeigt 5 der häufigsten Compiler-Optimierungen; Sie können die Unterschiede vor und nach der Optimierung direkt vergleichen.

⚡ Compiler Optimization: Make Code Faster Automatically

Choose an optimization technique and see how the compiler improves code

📝 Before optimization
const width = 10
const height = 20
const area = width * height  // computed at runtime
console.log(area)
Compiler optimization
🚀 After optimization
const area = 200  // computed during compilation
console.log(200)
How Constant folding works
The compiler sees that width and height are constants, so it computes 10 * 20 = 200 during compilation. Runtime no longer needs a multiplication.
Performance gain:
30%

Moderne Compiler und JIT-Engines (wie V8, GCC, LLVM) wenden automatisch Dutzende von Optimierungen an. Als Entwickler müssen Sie diese nicht manuell durchführen, aber das Verständnis hilft Ihnen:

  • Optimierfreundlicheren Code schreiben: z. B. const statt let verwenden — der Compiler kann Constant Folding leichter anwenden
  • Performance-Unterschiede verstehen: Warum sind kleine Funktionen schneller als große? Weil der Compiler sie inlinen kann
  • „De-Optimierung" vermeiden: Bestimmte Muster wie eval() und with verhindern Compiler-Optimierungen
OptimierungstechnikAuslöserPerformance-EinflussWas Entwickler tun können
Constant FoldingAusdruck enthält nur KonstantenLaufzeitberechnung entfälltMehr const-Deklarationen verwenden
Dead Code EliminationCode unerreichbar oder Ergebnis ungenutztCodegröße reduziertUngenutzten Code aufräumen
Loop Invariant Code MotionUnveränderliche Berechnung im LoopWiederholte Berechnung reduziertManuelles Extrahieren ist auch gut
Function InliningKleine Funktion wird häufig aufgerufenAufrufoverhead entfälltFunktionen klein und fokussiert halten
Constant PropagationVariablenwert zur Compile-Zeit bekanntGanze Berechnungskette entfälltKonstanten statt magischer Zahlen

7. Kompiliert vs. Interpretiert vs. JIT

Nachdem der Code geschrieben ist, gibt es drei „Übersetzungsarten", um ihn auszuführen. Jede hat ihre Vor- und Nachteile und bestimmt maßgeblich die Performance-Eigenschaften und Einsatzgebiete einer Sprache.

🔄 Compiled vs Interpreted vs JIT

Click an execution mode to see how code moves from source to running program

📝
Source code
main.c
⚙️
Compiler
Full compilation
📦
Machine code
Binary executable
🚀
Run directly
CPU runs it directly
Run speed
Very fast
Startup
Slow; compile first
Portability
Recompile required
Representative languages:CC++RustGo
DimensionKompiliertInterpretiertJIT (Just-In-Time)
AblaufZuerst vollständig zu Maschinencode kompilieren, dann ausführenZeilenweise lesen und ausführenZuerst interpretieren, Hotspot-Code dann kompilieren
AusführungsgeschwindigkeitAm schnellstenAm langsamstenMittel (Hotspots nahe an kompiliert)
StartgeschwindigkeitLangsam (Kompilierung nötig)Schnell (direkt ausführen)Mittel (Aufwärmen nötig)
PlattformunabhängigkeitNeukompilierung nötigNativerweise plattformunabhängigPlattformunabhängig
Repräsentative SprachenC, Rust, GoPython, RubyJavaScript (V8), Java

Warum ist JavaScript so schnell?

Der JIT-Compiler der V8-Engine überwacht, welcher Code häufig ausgeführt wird (Hotspot-Code), und kompiliert diesen dann zu hochoptimiertem Maschinencode. Obwohl JavaScript eine „interpretierte Sprache" ist, kann es in V8 eine Performance erreichen, die kompilierten Sprachen nahekommt. Das ist auch die Grundlage dafür, dass Node.js serverseitig eingesetzt werden kann.


Zusammenfassung

Compiler-Theorie ist kein Wissen, das nur Compiler-Entwickler brauchen. Das Verständnis des Kompilierungsprozesses hilft Ihnen, Fehlermeldungen besser zu verstehen, die richtige Sprache zu wählen und effizienteren Code zu schreiben.

Die wichtigsten Punkte dieses Kapitels:

  1. Der Compiler ist ein Dolmetscher: Übersetzt menschenlesbaren Code in maschinenausführbare Befehle
  2. Sechsstufige Pipeline: Lexikalische Analyse → Syntaxanalyse → Semantische Analyse → Zwischencode → Optimierung → Code-Generierung
  3. Lexikalische Analyse zerlegt in Token: Zeichenstrom in Schlüsselwörter, Bezeichner, Operatoren usw. aufteilen
  4. Syntaxanalyse baut den AST: Token nach Grammatikregeln in einer Baumstruktur anordnen, die Operatorprioritäten widerspiegelt
  5. Semantische Analyse sichert Korrektheit: Typprüfung, Gültigkeitsbereichsprüfung — die meisten Fehlermeldungen stammen von hier
  6. Automatische Compiler-Optimierung: Constant Folding, Dead Code Elimination, Function Inlining machen den Code automatisch schneller
  7. Drei Ausführungsmodelle: Kompiliert am schnellsten, interpretiert am flexibelsten, JIT vereint beides

Weiterführende Literatur