```
2. Syntaxisanalyse (parsen):
* Doel: Controleert of de volgorde van tokens voldoet aan de grammaticale regels (syntaxis) van de programmeertaal.
* Acties:
* Gebruikt de tokens om een abstracte syntaxisboom (AST) te bouwen. De AST vertegenwoordigt de hiërarchische structuur van het programma en laat zien hoe de tokens gerelateerd zijn.
* Identificeert syntaxisfouten (bijvoorbeeld ontbrekende puntkomma's, niet-overeenkomende haakjes).
* Uitvoer: Een abstracte syntaxisboom (AST).
Voorbeeld (vervolg van boven):
De parser zou een AST bouwen die de verklaring `int x =10 + y` vertegenwoordigt; De AST zou laten zien dat `x` wordt gedeclareerd als een geheel getal en wordt geïnitialiseerd met het resultaat van de uitdrukking `10 + y`.
3. Semantische analyse:
* Doel: Controleert de betekenis (semantiek) van de code en zorgt ervoor dat het programma logisch consistent is.
* Acties:
* Typecontrole:verifieert dat bewerkingen worden uitgevoerd op compatibele gegevenstypen (het toevoegen van een geheel getal aan een tekenreeks zou bijvoorbeeld een fout zijn).
* Scoperesolutie:bepaalt de betekenis van identificatiegegevens op basis van hun context (bijvoorbeeld naar welke variabele 'x' wordt verwezen).
* Foutdetectie:Identificeert semantische fouten (bijvoorbeeld door een niet-gedeclareerde variabele te gebruiken, een functie aan te roepen met het verkeerde aantal argumenten).
* Beheer van symbooltabellen:de symbooltabel slaat informatie op over identificaties (variabelen, functies, enz.) die in het programma worden gebruikt.
* Uitvoer: Een geannoteerde AST (de AST met aanvullende semantische informatie) en de symbolentabel.
4. Tussentijdse codegeneratie:
* Doel: Vertaalt de geannoteerde AST naar een tussenrepresentatie (IR).
* Acties:
* De IR is een machine-onafhankelijke weergave van het programma, ontworpen om eenvoudig te worden geoptimaliseerd en vertaald in doelcode. Veel voorkomende IR's omvatten code met drie adressen en stapelmachinecode.
* Vereenvoudigt de code en maakt het gemakkelijker om optimalisaties uit te voeren.
* Uitvoer: Tussencode (IR).
Voorbeeld (eenvoudige code met drie adressen):
De uitdrukking `10 + y` zou vertaald kunnen worden naar:
```
t1 =10 + y // t1 is een tijdelijke variabele
x =t1
```
5. Code-optimalisatie:
* Doel: Verbetert de efficiëntie van de tussencode.
* Acties:
* Er worden verschillende optimalisatietechnieken toegepast om de codegrootte, de uitvoeringstijd of beide te verminderen.
* Veel voorkomende optimalisaties zijn onder meer:
* Constant vouwen (het evalueren van constante expressies tijdens het compileren).
* Eliminatie van dode code (verwijdering van code die nooit wordt uitgevoerd).
* Lus afrollen (lussen uitbreiden om overhead te verminderen).
* Algemene eliminatie van subexpressies (het vermijden van overbodige berekeningen).
* Uitvoer: Geoptimaliseerde tussencode.
6. Code genereren:
* Doel: Vertaalt de geoptimaliseerde tussencode naar doelmachinecode (of assembleertaal).
* Acties:
* Selecteert de juiste machine-instructies voor elke IR-instructie.
* Wijst registers toe om variabelen en tussenwaarden op te slaan.
* Verwerkt geheugentoewijzing.
* Uitvoer: Machinecode of assembleertaal.
7. Montage (optioneel):
* Als de codegenerator assembleertaal uitvoert, wordt een assemblerprogramma gebruikt om de assemblagecode in machinecode om te zetten.
8. Koppelen (optioneel):
* Combineert meerdere objectbestanden (gecompileerde code uit verschillende bronbestanden) en bibliotheken in één uitvoerbaar bestand.
* Lost externe verwijzingen op (verwijzingen naar functies of variabelen die in andere bestanden zijn gedefinieerd).
Voorbeeldsamenvatting:
Stel dat u een eenvoudig C++-programma heeft:
```c++
int hoofd() {
int a =5;
int b =10;
int som =a + b;
retour 0;
}
```
De compiler zou het volgende (vereenvoudigde) proces doorlopen:
1. Lexicale analyse: Identificeert tokens zoals `int`, `main`, `=`, `5`, `+`, enz.
2. Syntaxisanalyse: Creëert een AST die de structuur van de code vertegenwoordigt (de functie `main` bevat bijvoorbeeld declaraties en een optelbewerking).
3. Semantische analyse: Controleert of variabelen vóór gebruik worden gedeclareerd, of `+` geldig is voor gehele getallen, enz.
4. Tussentijdse codegeneratie: Creëert een tussenrepresentatie, misschien zoiets als:
```
een =5
b =10
som =a + b
retour 0
```
5. Code-optimalisatie: Kan kleine optimalisaties uitvoeren (in dit triviale geval is er niet veel optimalisatie mogelijk).
6. Code genereren: Vertaalt de IR naar machinecode-instructies die specifiek zijn voor de doelprocessor (bijvoorbeeld x86-assemblage-instructies).
Belangrijkste voordelen van compilatie:
* Snelheid: Gecompileerde code werkt over het algemeen sneller dan geïnterpreteerde code, omdat de vertaling slechts één keer wordt uitgevoerd, tijdens het compileren, en niet telkens wanneer het programma wordt uitgevoerd.
* Foutdetectie: Compilers kunnen tijdens het compileren veel fouten ontdekken, voordat het programma ooit wordt uitgevoerd, waardoor de betrouwbaarheid van de code wordt verbeterd.
* Beveiliging: Door fouten tijdens het compileren te detecteren, kunnen compilers bepaalde beveiligingsproblemen helpen voorkomen.
Belangrijkste nadelen van compilatie:
* Platformafhankelijkheid: Gecompileerde code is vaak platformspecifiek, wat betekent dat deze alleen kan worden uitgevoerd op het besturingssysteem en de processorarchitectuur waarvoor deze is gecompileerd.
* Compileertijd: Het compilatieproces kan een aanzienlijke hoeveelheid tijd in beslag nemen, vooral bij grote programma's.
* Debuggen van complexiteit: Het debuggen van gecompileerde code kan soms een grotere uitdaging zijn dan het debuggen van geïnterpreteerde code, omdat u met machinecode werkt in plaats van met de originele broncode.
Samenvattend is de compiler een cruciaal onderdeel van softwareontwikkeling, verantwoordelijk voor het transformeren van code op hoog niveau in een vorm die een computer kan uitvoeren. Het voert verschillende complexe fasen uit om machinecode te analyseren, optimaliseren en genereren, waardoor we uiteindelijk de programma's die we schrijven kunnen uitvoeren.