Die Kompilierung eines C ++ - Programms umfasst drei Schritte:
Vorverarbeitung: Der Präprozessor nimmt eine C ++ - Quellcodedatei und behandelt die Anweisungen #include
s, #define
s und andere Präprozessoren. Die Ausgabe dieses Schritts ist eine "reine" C ++ - Datei ohne Vorprozessoranweisungen.
Kompilierung: Der Compiler nimmt die Ausgabe des Vorprozessors und erstellt daraus eine Objektdatei.
Verknüpfen: Der Linker nimmt die vom Compiler erstellten Objektdateien und erstellt entweder eine Bibliothek oder eine ausführbare Datei.
Vorverarbeitung
Der Präprozessor verarbeitet die Präprozessoranweisungen wie #include
und #define
. Es ist unabhängig von der Syntax von C ++, weshalb es mit Vorsicht verwendet werden muss.
Es funktioniert auf einer C ++ Quelldatei zu einem Zeitpunkt durch Ersetzen #include
Richtlinien mit dem Inhalt der jeweiligen Dateien (die in der Regel nur Erklärungen sind), von Makros (tun Ersatz #define
), und die Auswahl unterschiedliche Teile des Textes in Abhängigkeit von #if
, #ifdef
und #ifndef
Richtlinien.
Der Präprozessor arbeitet mit einem Strom von Vorverarbeitungstoken. Makrosubstitution ist definiert als Ersetzen von Token durch andere Token (der Operator ##
ermöglicht das Zusammenführen von zwei Token, wenn dies sinnvoll ist).
Nach alledem erzeugt der Präprozessor eine einzelne Ausgabe, die ein Strom von Token ist, die aus den oben beschriebenen Transformationen resultieren. Außerdem werden einige spezielle Markierungen hinzugefügt, die dem Compiler mitteilen, woher die einzelnen Zeilen stammen, damit diese sinnvolle Fehlermeldungen erzeugen können.
In dieser Phase können durch geschickte Verwendung der Direktiven #if
und einige Fehler auftreten #error
.
Zusammenstellung
Der Kompilierungsschritt wird an jedem Ausgang des Präprozessors ausgeführt. Der Compiler analysiert den reinen C ++ - Quellcode (jetzt ohne Präprozessoranweisungen) und konvertiert ihn in Assemblycode. Ruft dann das zugrunde liegende Back-End (Assembler in der Toolchain) auf, das diesen Code zu Maschinencode zusammensetzt und eine tatsächliche Binärdatei in einem bestimmten Format (ELF, COFF, a.out, ...) erzeugt. Diese Objektdatei enthält den kompilierten Code (in binärer Form) der in der Eingabe definierten Symbole. Symbole in Objektdateien werden mit Namen bezeichnet.
Objektdateien können sich auf Symbole beziehen, die nicht definiert sind. Dies ist der Fall, wenn Sie eine Deklaration verwenden und keine Definition dafür angeben. Der Compiler hat nichts dagegen und wird die Objektdatei gerne erstellen, solange der Quellcode wohlgeformt ist.
Mit Compilern können Sie die Kompilierung normalerweise an dieser Stelle beenden. Dies ist sehr nützlich, da Sie damit jede Quellcodedatei separat kompilieren können. Dies bietet den Vorteil, dass Sie nicht alles neu kompilieren müssen, wenn Sie nur eine einzelne Datei ändern.
Die erstellten Objektdateien können in speziellen Archiven, so genannten statischen Bibliotheken, abgelegt werden, um sie später leichter wiederverwenden zu können.
In diesem Stadium werden "normale" Compilerfehler wie Syntaxfehler oder fehlgeschlagene Überlastungsauflösungsfehler gemeldet.
Verknüpfen
Der Linker erzeugt die endgültige Kompilierungsausgabe aus den vom Compiler erstellten Objektdateien. Diese Ausgabe kann entweder eine gemeinsam genutzte (oder dynamische) Bibliothek sein (und obwohl der Name ähnlich ist, haben sie mit den zuvor erwähnten statischen Bibliotheken nicht viel gemeinsam) oder eine ausführbare Datei.
Es verknüpft alle Objektdateien, indem die Verweise auf undefinierte Symbole durch die richtigen Adressen ersetzt werden. Jedes dieser Symbole kann in anderen Objektdateien oder in Bibliotheken definiert werden. Wenn sie in anderen Bibliotheken als der Standardbibliothek definiert sind, müssen Sie dem Linker davon erzählen.
Zu diesem Zeitpunkt sind die häufigsten Fehler fehlende Definitionen oder doppelte Definitionen. Ersteres bedeutet, dass entweder die Definitionen nicht vorhanden sind (dh nicht geschrieben sind) oder dass die Objektdateien oder Bibliotheken, in denen sie sich befinden, nicht an den Linker übergeben wurden. Letzteres ist offensichtlich: Das gleiche Symbol wurde in zwei verschiedenen Objektdateien oder Bibliotheken definiert.