Namensräume
Varianten
Aktionen

Übersetzungsphasen

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

C++-Quellcodedateien werden vom Compiler verarbeitet, um C++-Programme zu erzeugen.

Inhalt

[edit] Übersetzungsprozess

Der Text eines C++-Programms wird in Einheiten namens Quellcodedateien gehalten.

C++-Quellcodedateien durchlaufen eine Übersetzung, um eine Übersetzungseinheit zu werden, bestehend aus den folgenden Schritten:

  1. Ordnet jede Quelldatei einer Zeichensequenz zu.
  2. Konvertiert jede Zeichensequenz in eine Sequenz von Präprozessor-Tokens, getrennt durch Leerzeichen.
  3. Konvertiert jedes Präprozessor-Token in ein Token und bildet eine Token-Sequenz.
  4. Konvertiert jede Token-Sequenz in eine Übersetzungseinheit.

Ein C++-Programm kann aus übersetzten Übersetzungseinheiten gebildet werden. Übersetzungseinheiten können separat übersetzt und später gelinkt werden, um ein ausführbares Programm zu erzeugen.

Der obige Prozess kann in 9 Übersetzungsphasen organisiert werden.

[edit] Präprozessor-Tokens

Ein Präprozessor-Token ist das minimale lexikalische Element der Sprache in den Übersetzungsphasen 3 bis 6.

Die Kategorien von Präprozessor-Tokens sind:

  • Platzhalter-Tokens, die durch Präprozessor-Anweisungen import und module erzeugt werden (d. h. import XXX; und module XXX;)
(seit C++20)
Das Programm ist fehlerhaft, wenn das Zeichen, das dieser Kategorie entspricht, Folgendes ist:
  • Apostroph (', U+0027),
  • Anführungszeichen (", U+0022) oder
  • ein Zeichen, das nicht im Basiszeichensatz enthalten ist.

[edit] Präprozessor-Zahlen

Die Menge der Präprozessor-Tokens von Präprozessor-Zahlen ist eine Obermenge der Vereinigung der Mengen von Tokens von Ganzzahl-Literalen und Gleitkomma-Literalen

.(optional) Ziffer pp-continue-seq (optional)
Ziffer - eine der Ziffern 0-9
pp-continue-seq - eine Sequenz von pp-continue s

Jedes pp-continue ist eines der folgenden:

identifier-continue (1)
exp-char sign-char (2)
. (3)
Ziffer (4) (seit C++14)
nondigit (5) (seit C++14)
identifier-continue - jedes Zeichen außer dem ersten eines gültigen Bezeichners
exp-char - eines von P, p, (seit C++11) E und e
sign-char - eines von + und -
Ziffer - eine der Ziffern 0-9
nondigit - eines der lateinischen Buchstaben A/a-Z/z und Unterstrich

Eine Präprozessor-Zahl hat keinen Typ oder Wert; sie erhält beides nach einer erfolgreichen Konvertierung in ein Ganzzahl-/Gleitkomma-Literal-Token.

[edit] Leerzeichen

Leerzeichen besteht aus Kommentaren, Leerzeichen oder beidem.

Die folgenden Zeichen sind Leerzeichen:

  • Zeichen-Tabulator (U+0009)
  • Zeilenumbruch / Zeilenvorschub-Zeichen (U+000A)
  • Zeilentabulator (U+000B)
  • Seitenumbruch (U+000C)
  • Leerzeichen (U+0020)

Leerzeichen werden normalerweise verwendet, um Präprozessor-Tokens zu trennen, mit den folgenden Ausnahmen:

  • Es ist kein Trennzeichen in Header-Namen, Zeichenliteralen und String-Literalen.
  • Durch Leerzeichen, die Zeilenumbrüche enthalten, getrennte Präprozessor-Tokens können keine Präprozessor-Anweisungen bilden.
#include "my header"        // OK, using a header name containing whitespace
 
#include/*hello*/<iostream> // OK, using a comment as whitespace
 
#include
<iostream> // Error: #include cannot span across multiple lines
 
"str ing"  // OK, a single preprocessing token (string literal)
' '        // OK, a single preprocessing token (character literal)

[edit] Maximaler Munch

Wenn die Eingabe bis zu einem bestimmten Zeichen in Präprozessor-Tokens zerlegt wurde, wird das nächste Präprozessor-Token im Allgemeinen als die längste Zeichensequenz genommen, die ein Präprozessor-Token bilden könnte, auch wenn dies zu einem späteren Analysefehler führen würde. Dies ist allgemein bekannt als maximaler Munch.

int foo = 1;
int bar = 0xE+foo;   // Error: invalid preprocessing number 0xE+foo
int baz = 0xE + foo; // OK

Mit anderen Worten, die Regel des maximalen Munch begünstigt mehrere Zeichen umfassende Operatoren und Satzzeichen.

int foo = 1;
int bar = 2;
 
int num1 = foo+++++bar; // Error: treated as “foo++ ++ +baz”, not “foo++ + ++baz”
int num2 = -----foo;    // Error: treated as “-- -- -foo”, not “- -- --foo”

Die Regel des maximalen Munch hat die folgenden Ausnahmen:

  • Header-Namen-Präprozessor-Tokens werden nur in folgenden Fällen gebildet:
  • nach dem Präprozessor-Token include in einer #include-Anweisung
(seit C++17)
  • nach dem Präprozessor-Token import in einer import-Anweisung
(seit C++20)
std::vector<int> x; // OK, “int” is not a header name
  • Wenn die nächsten drei Zeichen <:: sind und das nachfolgende Zeichen weder : noch > ist, wird das < als einzelnes Präprozessor-Token behandelt und nicht als erstes Zeichen des alternativen Tokens <:.
struct Foo { static const int v = 1; };
std::vector<::Foo> x;  // OK, <: not taken as the alternative token for [
extern int y<::>;      // OK, same as “extern int y[];”
int z<:::Foo::value:>; // OK, same as “int z[::Foo::value];”
  • Wenn die nächsten beiden Zeichen >> sind und eines der >-Zeichen einen Template-Bezeichner vervollständigen kann, wird das Zeichen als einzelnes Präprozessor-Token behandelt und nicht als Teil des Präprozessor-Tokens >>.
template<int i> class X { /* ... */ };
template<class T> class Y { /* ... */ };
 
Y<X<1>> x3;      // OK, declares a variable “x3” of type “Y<X<1> >”
Y<X<6>>1>> x4;   // Syntax error
Y<X<(6>>1)>> x5; // OK
  • Wenn das nächste Zeichen eine Sequenz von Zeichen einleitet, die das Präfix und das anfängliche Anführungszeichen eines Raw-String-Literals sein könnten, ist das nächste Präprozessor-Token ein Raw-String-Literal. Das Literal besteht aus der kürzesten Zeichensequenz, die dem Raw-String-Muster entspricht.
#define R "x"
const char* s = R"y";         // ill-formed raw string literal, not "x" "y"
const char* s2 = R"(a)" "b)"; // a raw string literal followed by a normal string literal
(seit C++11)

[edit] Tokens

Ein Token ist das minimale lexikalische Element der Sprache in Übersetzungsphase 7.

Die Kategorien von Tokens sind:

[edit] Übersetzungsphasen

Die Übersetzung wird as if in der Reihenfolge von Phase 1 bis Phase 9 durchgeführt. Implementierungen verhalten sich so, als ob diese separaten Phasen stattfinden, obwohl in der Praxis verschiedene Phasen zusammengelegt werden können.

[edit] Phase 1: Abbildung von Quellzeichen

1) Die einzelnen Bytes der Quellcodedatei werden (auf implementierungsabhängige Weise) auf die Zeichen des Basiszeichensatzes der Quelle abgebildet. Insbesondere werden betriebssystemabhängige Zeilenende-Indikatoren durch neue Zeilenzeichen ersetzt.
2) Der Satz der akzeptierten Quellcodedateizeichen ist implementierungsabhängig(seit C++11). Jedes Quellcodedateizeichen, das nicht auf ein Zeichen im Basiszeichensatz der Quelle abgebildet werden kann, wird durch seinen universellen Zeichentitel (escaped mit \u oder \U) oder durch eine implementierungsabhängige Form ersetzt, die äquivalent behandelt wird.
3) Trigraph-Sequenzen werden durch entsprechende Ein-Zeichen-Darstellungen ersetzt.
(bis C++17)
(bis C++23)

Eingabedateien, die eine Sequenz von UTF-8-Codeeinheiten (UTF-8-Dateien) sind, werden garantiert unterstützt. Die Menge anderer unterstützter Arten von Eingabedateien ist implementierungsabhängig. Wenn die Menge nicht leer ist, wird die Art einer Eingabedatei auf implementierungsabhängige Weise bestimmt, die eine Möglichkeit zur Bezeichnung von Eingabedateien als UTF-8-Dateien einschließt, unabhängig von ihrem Inhalt (das Erkennen des Byte Order Mark ist nicht ausreichend).

  • Wenn eine Eingabedatei als UTF-8-Datei bestimmt wird, muss sie eine wohlgeformte UTF-8-Codeeinheiten-Sequenz sein, und sie wird dekodiert, um eine Sequenz von Unicode-Skalarwerten zu erzeugen. Eine Sequenz von Elementen des Übersetzungszeichensatzes wird dann gebildet, indem jeder Unicode-Skalarwert auf das entsprechende Element des Übersetzungszeichensatzes abgebildet wird. In der resultierenden Sequenz wird jedes Paar von Zeichen in der Eingabesequenz, das aus einem Wagenrücklauf (U+000D) gefolgt von einem Zeilenvorschub (U+000A) besteht, sowie jeder Wagenrücklauf (U+000D), dem nicht unmittelbar ein Zeilenvorschub (U+000A) folgt, durch ein einzelnes Zeilenvorschubzeichen ersetzt.
  • Für jede andere Art von Eingabedatei, die von der Implementierung unterstützt wird, werden Zeichen (auf implementierungsabhängige Weise) auf eine Sequenz von Elementen des Übersetzungszeichensatzes abgebildet. Insbesondere werden betriebssystemabhängige Zeilenende-Indikatoren durch Zeilenvorschubzeichen ersetzt.
(seit C++23)

[edit] Phase 2: Zusammenfügen von Zeilen

1) Wenn das erste Übersetzungszeichen ein Byte Order Mark (U+FEFF) ist, wird es gelöscht.(seit C++23)Wenn ein Backslash (\) am Ende einer Zeile steht (unmittelbar gefolgt von null oder mehr Leerzeichen außer Zeilenumbruch, gefolgt von(seit C++23) dem Zeilenumbruchzeichen), werden diese Zeichen gelöscht, wodurch zwei physische Quellzeilen zu einer logischen Quellzeile zusammengefügt werden. Dies ist eine Ein-Pass-Operation; eine Zeile, die mit zwei Backslashes endet, gefolgt von einer leeren Zeile, fügt keine drei Zeilen zu einer zusammen.
2) Wenn eine nicht leere Quelldatei nach diesem Schritt (Ende-der-Zeile-Backslashes sind zu diesem Zeitpunkt keine Spleiße mehr) nicht mit einem Zeilenvorschubzeichen endet, wird ein abschließendes Zeilenvorschubzeichen hinzugefügt.

[edit] Phase 3: Lexikalische Analyse

1) Die Quelldatei wird in Präprozessor-Tokens und Leerzeichen zerlegt.
// The following #include directive can de decomposed into 5 preprocessing tokens:
 
//     punctuators (#, < and >)
//          │
// ┌────────┼────────┐
// │        │        │
   #include <iostream>
//     │        │
//     │        └── header name (iostream)
//     │
//     └─────────── identifier (include)
Wenn eine Quelldatei mit einem teilweisen Präprozessor-Token oder einem teilweisen Kommentar endet, ist das Programm fehlerhaft.
// Error: partial string literal
"abc
// Error: partial comment
/* comment
Während Zeichen aus der Quelldatei verbraucht werden, um das nächste Präprozessor-Token zu bilden (d. h. nicht als Teil eines Kommentars oder anderer Formen von Leerzeichen verbraucht werden), werden universelle Zeichentitel erkannt und durch das bezeichnete Element des Übersetzungszeichensatzes ersetzt, außer wenn eine Zeichensequenz in einem der folgenden Präprozessor-Tokens abgeglichen wird:
  • ein Zeichenliteral (c-char-sequence)
  • ein String-Literal (s-char-sequence und r-char-sequence), ausgenommen Begrenzer (d-char-sequence)
  • ein Header-Name (h-char-sequence und q-char-sequence)
(seit C++23)


2) Alle Transformationen, die während Phase 1 und(bis C++23) Phase 2 zwischen dem anfänglichen und dem abschließenden Anführungszeichen eines beliebigen Raw-String-Literals durchgeführt wurden, werden rückgängig gemacht.
(seit C++11)
3) Leerzeichen werden transformiert:
  • Jeder Kommentar wird durch ein Leerzeichen ersetzt.
  • Zeilenumbruchzeichen bleiben erhalten.
  • Ob jede nicht leere Sequenz von Leerzeichen außer Zeilenumbruch erhalten bleibt oder durch ein Leerzeichen ersetzt wird, ist nicht spezifiziert.

[edit] Phase 4: Präprozessor

1) Der Präprozessor wird ausgeführt.
2) Jede Datei, die mit der #include-Anweisung eingeführt wurde, durchläuft rekursiv die Phasen 1 bis 4.
3) Am Ende dieser Phase werden alle Präprozessor-Anweisungen aus der Quelle entfernt.

[edit] Phase 5: Bestimmung gemeinsamer String-Literal-Kodierungen

1) Alle Zeichen in Zeichenliteralen und String-Literalen werden vom Quellzeichensatz in die Kodierung konvertiert (was eine Multibyte-Zeichenkodierung wie UTF-8 sein kann, solange die 96 Zeichen des Basiszeichensatzes Ein-Byte-Darstellungen haben).
2) Escape-Sequenzen und universelle Zeichentitel in Zeichenliteralen und Nicht-Raw-String-Literalen werden erweitert und in die Literal-Kodierung konvertiert.

Wenn das Zeichen, das durch einen universellen Zeichentitel spezifiziert wird, nicht als einzelner Code-Punkt in der entsprechenden Literal-Kodierung kodiert werden kann, ist das Ergebnis implementierungsabhängig, aber es wird garantiert, dass es kein Null- (breites) Zeichen ist.

(bis C++23)

Für eine Sequenz von zwei oder mehr benachbarten String-Literal-Tokens wird ein gemeinsames Kodierungspräfix bestimmt, wie hier beschrieben. Jedes solche String-Literal-Token gilt dann als dieses gemeinsame Kodierungspräfix habend. (Zeichenkonvertierung wird in Phase 3 verschoben)

(seit C++23)

[edit] Phase 6: Verketten von String-Literalen

Benachbarte String-Literale werden verkettet.

[edit] Phase 7: Kompilieren

Die Kompilierung findet statt: jedes Präprozessor-Token wird in ein Token konvertiert. Die Tokens werden syntaktisch und semantisch analysiert und als Übersetzungseinheit übersetzt.

[edit] Phase 8: Instanziieren von Templates

Jede Übersetzungseinheit wird untersucht, um eine Liste der erforderlichen Template-Instanziierungen zu erzeugen, einschließlich derjenigen, die durch explizite Instanziierungen angefordert werden. Die Definitionen der Templates werden gefunden, und die erforderlichen Instanziierungen werden durchgeführt, um Instanziierungseinheiten zu erzeugen.

[edit] Phase 9: Linken

Übersetzungseinheiten, Instanziierungseinheiten und Bibliothekskomponenten, die zur Erfüllung externer Referenzen benötigt werden, werden zu einem Programm-Image gesammelt, das Informationen enthält, die für die Ausführung in seiner Ausführungsumgebung benötigt werden.

[edit] Anmerkungen

Quellcodedateien, Übersetzungseinheiten und übersetzte Übersetzungseinheiten müssen nicht unbedingt als Dateien gespeichert werden, noch muss es eine Eins-zu-Eins-Korrespondenz zwischen diesen Entitäten und einer externen Darstellung geben. Die Beschreibung ist rein konzeptionell und gibt keine bestimmte Implementierung an.

Die in Phase 5 durchgeführte Konvertierung kann in einigen Implementierungen durch Kommandozeilenoptionen gesteuert werden: gcc und clang verwenden -finput-charset, um die Kodierung des Quellzeichensatzes anzugeben, -fexec-charset und -fwide-exec-charset, um die normalen und breiten Literal-Kodierungen anzugeben, während Visual Studio 2015 Update 2 und neuere Versionen /source-charset und /execution-charset verwenden, um den Quellzeichensatz und die Literal-Kodierung anzugeben.

(bis C++23)

Einige Compiler implementieren keine Instanziierungseinheiten (auch bekannt als Template-Repositories oder Template-Registries) und kompilieren jede Template-Instanziierung einfach in Phase 7, speichern den Code in der Objektdatei, wo er implizit oder explizit angefordert wird, und der Linker kollabiert dann diese kompilierten Instanziierungen in Phase 9 zu einer.

[edit] Fehlerberichte

Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 787 C++98 das Verhalten war undefiniert, wenn eine nicht leere Quelldatei
am Ende von Phase 2 keinen Zeilenumbruch hatte
ein abschließendes Newline-Zeichen hinzufügen
Zeichen in diesem Fall
CWG 1104 C++98 das alternative Token <: führte dazu, dass std::vector<::std::string>
als std::vector[:std::string> behandelt wurde
eine zusätzliche lexikalische
Regel hinzugefügt, um diesen Fall zu behandeln
CWG 1775 C++11 das Bilden eines universellen Zeichentitels innerhalb eines Raw-
String-Literals in Phase 2 führte zu undefiniertem Verhalten
wurde wohlformuliert
CWG 2747 C++98 Phase 2 überprüfte den End-of-File-Splice nach dem Spleißen, dies ist unnötig die Überprüfung wurde entfernt
P2621R3 C++98 universelle Zeichentitel durften nicht
durch Zeilen-Spleißen oder Token-Verkettung gebildet werden
erlaubt

[edit] Referenzen

  • C++23 Standard (ISO/IEC 14882:2024)
  • 5.2 Phasen der Übersetzung [lex.phases]
  • C++20 Standard (ISO/IEC 14882:2020)
  • 5.2 Phasen der Übersetzung [lex.phases]
  • C++17 Standard (ISO/IEC 14882:2017)
  • 5.2 Phasen der Übersetzung [lex.phases]
  • C++14 Standard (ISO/IEC 14882:2014)
  • 2.2 Phasen der Übersetzung [lex.phases]
  • C++11 Standard (ISO/IEC 14882:2011)
  • 2.2 Phasen der Übersetzung [lex.phases]
  • C++03-Standard (ISO/IEC 14882:2003)
  • 2.1 Phasen der Übersetzung [lex.phases]
  • C++98 Standard (ISO/IEC 14882:1998)
  • 2.1 Phasen der Übersetzung [lex.phases]

[edit] Siehe auch

C-Dokumentation für Phasen der Übersetzung