Namespaces
Variants

Phases of translation

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

C++-Quelldateien werden vom Compiler verarbeitet, um C++-Programme zu erstellen.

Inhaltsverzeichnis

Übersetzungsprozess

Der Text eines C++-Programms wird in Einheiten namens source files gespeichert.

C++-Quelldateien durchlaufen Translation , um eine Translation Unit zu werden, die aus den folgenden Schritten besteht:

  1. Ordnet jede Quelldatei einer Zeichenfolge zu.
  2. Konvertiert jede Zeichenfolge in eine Präprozessor-Token-Sequenz, 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. Übersetzte Übersetzungseinheiten und instanziierte Einheiten (instanziierte Einheiten werden in Phase 8 unten beschrieben) können individuell gespeichert oder in einer Bibliothek gespeichert werden. Mehrere Übersetzungseinheiten kommunizieren miteinander durch (beispielsweise) Symbole mit externer Bindung oder Datendateien. Übersetzungseinheiten können separat übersetzt und später gebunden werden, um ein ausführbares Programm zu erzeugen.

Der oben beschriebene Prozess kann in 9 Übersetzungsphasen unterteilt werden.

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-Token sind:

(seit C++20)
Das Programm ist fehlerhaft, wenn das Zeichen, das dieser Kategorie entspricht,
  • Apostroph ( ' , U+0027),
  • Anführungszeichen ( " , U+0022), oder
  • ein Zeichen außerhalb des Basiszeichensatzes ist.

Präprozessorzahlen

Die Menge der Präprozessor-Tokens einer Präprozessornummer ist eine Obermenge der Vereinigung der Mengen der Tokens von Integer-Literalen und Gleitkomma-Literalen :

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

Jeder pp-continue ist einer der Folgenden:

identifier-continue (1)
exp-char sign-char (2)
. (3)
digit (4) (seit C++14)
nondigit (5) (seit C++14)
identifier-continue - jedes Nicht-Erst-Zeichen eines gültigen Identifikators
exp-char - eines der Zeichen P , p , (seit C++11) E und e
sign-char - eines der Zeichen + und -
digit - eine der Ziffern 0-9
nondigit - eines der lateinischen Buchstaben A/a-Z/z und Unterstrich

Eine Präprozessornummer hat keinen Typ oder Wert; sie erhält beides nach einer erfolgreichen Umwandlung in ein Integer-/Fließkomma-Literal-Token.

Whitespace

Whitespace besteht aus Kommentaren , Whitespace-Zeichen oder beidem.

Die folgenden Zeichen sind Leerzeichen:

  • Zeichentabulator (U+0009)
  • Zeilenvorschub / Zeilenumbruchzeichen (U+000A)
  • Zeilentabulator (U+000B)
  • Seitenvorschub (U+000C)
  • Leerzeichen (U+0020)

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

  • Es ist kein Trennzeichen in Header-Namen, Zeichenliteralen und Zeichenkettenliteralen.
  • Präprozessortoken, die durch Leerzeichen mit Zeilenumbruchzeichen getrennt sind, können keine Präprozessordirektiven bilden.
#include "my header"        // OK, Verwendung eines Header-Namens mit Leerzeichen
#include/*hello*/<iostream> // OK, Verwendung eines Kommentars als Leerzeichen
#include
<iostream> // Fehler: #include kann sich nicht über mehrere Zeilen erstrecken
"str ing"  // OK, ein einzelner Präprozessor-Token (String-Literal)
' '        // OK, ein einzelner Präprozessor-Token (Zeichen-Literal)

Maximal Munch

Die maximale Munch ist die Regel, die in Phase 3 bei der Zerlegung der Quelldatei in Präprozessor-Token verwendet wird.

Wenn die Eingabe bis zu einem bestimmten Zeichen in Präprozessor-Token geparst wurde (ansonsten wird das nächste Präprozessor-Token nicht geparst, was die Parsing-Reihenfolge eindeutig macht), wird das nächste Präprozessor-Token generell als die längste Zeichensequenz genommen, die ein Präprozessor-Token bilden könnte, selbst wenn dies dazu führt, dass die nachfolgende Analyse fehlschlägt. Dies ist allgemein bekannt als maximal munch .

int foo = 1;
int bar = 0xE+foo;   // Fehler: ungültige Präprozessor-Zahl 0xE+foo
int baz = 0xE + foo; // OK

Mit anderen Worten, die Maximal-Munch-Regel begünstigt mehrzeilige Operatoren und Interpunktionszeichen :

int foo = 1;
int bar = 2;
int num1 = foo+++++bar; // Fehler: wird als "foo++ ++ +baz" interpretiert, nicht als "foo++ + ++baz"
int num2 = -----foo;    // Fehler: wird als "-- -- -foo" interpretiert, nicht als "- -- --foo"

Die Maximal-Munch-Regel hat die folgenden Ausnahmen:

  • Header-Name-Präprozessor-Token werden nur in den folgenden Fällen gebildet:
  • nach dem include Präprozessor-Token in einer #include Direktive
(seit C++17)
  • nach dem import Präprozessor-Token in einer import Direktive
(seit C++20)
std::vector<int> x; // OK, „int“ ist kein Header-Name
  • Wenn die nächsten drei Zeichen < :: sind und das folgende Zeichen weder : noch > ist, wird das < als eigenständiges Präprozessor-Token behandelt anstatt als erstes Zeichen des alternativen Tokens < : .
struct Foo { static const int v = 1; };
std::vector<::Foo> x;  // OK, <: nicht als Alternativtoken für [ interpretiert
extern int y<::>;      // OK, gleichbedeutend mit "extern int y[];"
int z<:::Foo::value:>; // OK, gleichbedeutend mit "int z[::Foo::value];"
  • Wenn die nächsten zwei Zeichen >> sind und eines der > Zeichen eine Template-Kennung vervollständigen kann, wird das Zeichen als eigenständiger Präprozessor-Token behandelt, anstatt Teil des Präprozessor-Tokens >> zu sein.
template<int i> class X { /* ... */ };
template<class T> class Y { /* ... */ };
Y<X<1>> x3;      // OK, deklariert eine Variable „x3“ vom Typ „Y<X<1> >“
Y<X<6>>1>> x4;   // Syntaxfehler
Y<X<(6>>1)>> x5; // OK
  • Wenn das nächste Zeichen eine Zeichenfolge beginnt, die das Präfix und das anfängliche doppelte Anführungszeichen eines Raw-String-Literals sein könnte, ist der nächste Präprozessor-Token ein Raw-String-Literal. Das Literal besteht aus der kürzesten Zeichenfolge, die dem Raw-String-Muster entspricht.
#define R "x"
const char* s = R"y";         // ungültiges Raw-String-Literal, nicht "x" "y"
const char* s2 = R"(a)" "b)"; // ein Raw-String-Literal gefolgt von einem normalen String-Literal
(seit C++11)

Token

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

Die Kategorien von Token sind:

Übersetzungsphasen

Die Übersetzung erfolgt as if in der Reihenfolge von Phase 1 bis Phase 9. Implementierungen verhalten sich so, als ob diese separaten Phasen auftreten, obwohl in der Praxis verschiedene Phasen zusammengefasst werden können.

Phase 1: Zuordnung von Quellzeichen

1) Die einzelnen Bytes der Quellcodedatei werden (auf implementierungsdefinierte Weise) auf die Zeichen des basic source character set abgebildet. Insbesondere werden betriebssystemabhängige Zeilenende-Kennungen durch Zeilenumbruchzeichen ersetzt.
2) Der Satz akzeptierter Quellcodedateizeichen ist implementierungsdefiniert (since C++11) . Jedes Quellcodedateizeichen, das nicht auf ein Zeichen im basic source character set abgebildet werden kann, wird durch seinen universal character name (mit \u oder \U escaped) oder durch eine implementierungsdefinierte Form, die gleichwertig behandelt wird, ersetzt.
3) Trigraph sequences werden durch entsprechende Einzelzeichen-Darstellungen ersetzt.
(until C++17)
(until C++23)

Eingabedateien, die eine Folge von UTF-8-Codeeinheiten sind (UTF-8-Dateien), werden garantiert unterstützt. Der Satz anderer unterstützter Arten von Eingabedateien ist implementierungsdefiniert. Falls der Satz nicht leer ist, wird die Art einer Eingabedatei auf implementierungsdefinierte Weise bestimmt, die ein Mittel zur Kennzeichnung von Eingabedateien als UTF-8-Dateien unabhängig von ihrem Inhalt beinhaltet (das Erkennen der Byte-Reihenfolge-Markierung ist nicht ausreichend).

  • Wenn eine Eingabedatei als UTF-8-Datei bestimmt wird, dann muss sie eine wohlgeformte UTF-8-Codeeinheitenfolge sein und wird decodiert, um eine Folge von Unicode-Skalarwerten zu erzeugen. Eine Folge von translation character set -Elementen wird dann gebildet, indem jeder Unicode-Skalarwert auf das entsprechende Übersetzungszeichensatz-Element abgebildet wird. In der resultierenden Folge wird jedes Paar von Zeichen in der Eingabefolge, das aus Wagenrücklauf (U+000D) gefolgt von Zeilenvorschub (U+000A) besteht, sowie jeder Wagenrücklauf (U+000D), dem nicht unmittelbar ein Zeilenvorschub (U+000A) folgt, durch ein einzelnes Zeilenumbruchzeichen ersetzt.
  • Für jede andere Art von Eingabedatei, die von der Implementierung unterstützt wird, werden Zeichen (auf implementierungsdefinierte Weise) auf eine Folge von Übersetzungszeichensatz-Elementen abgebildet. Insbesondere werden betriebssystemabhängige Zeilenende-Kennungen durch Zeilenumbruchzeichen ersetzt.
(since C++23)

Phase 2: Zeilenverknüpfung

1) Wenn das erste Übersetzungszeichen die Byte-Reihenfolge-Markierung (U+FEFF) ist, wird sie gelöscht. (since C++23) Immer wenn ein Backslash ( \ ) am Ende einer Zeile erscheint (unmittelbar gefolgt von null oder mehr Leerzeichen außer Zeilenumbruch gefolgt von (since C++23) dem Zeilenumbruchzeichen), werden diese Zeichen gelöscht, wodurch zwei physische Quellzeilen zu einer logischen Quellzeile kombiniert werden. Dies ist ein Einzeldurchlauf-Vorgang; eine Zeile, die mit zwei Backslashes endet, gefolgt von einer leeren Zeile, kombiniert nicht drei Zeilen zu einer.
2) Wenn eine nicht-leere Quelldatei nach diesem Schritt nicht mit einem Newline-Zeichen endet (Zeilenende-Backslashes sind an diesem Punkt keine Spleiße mehr), wird ein abschließendes Newline-Zeichen hinzugefügt.

Phase 3: Lexing

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 unvollständigen Präprozessor-Token oder einem unvollständigen Kommentar endet, ist das Programm fehlerhaft:
// Error: partial string literal
"abc
// Error: partial comment
/* comment
Wenn Zeichen aus der Quelldatei verarbeitet werden, um das nächste Präprozessor-Token zu bilden (d.h., nicht als Teil eines Kommentars oder anderer Formen von Leerraum), werden universelle Zeichennamen erkannt und durch das entsprechende Element des Übersetzungszeichensatzes ersetzt, außer beim Abgleichen einer Zeichenfolge in einem der folgenden Präprozessor-Tokens:
  • ein Zeichenliteral ( c-char-sequence )
  • ein Zeichenkettenliteral ( s-char-sequence und r-char-sequence ), ohne Trennzeichen ( d-char-sequence )
  • ein Header-Name ( h-char-sequence und q-char-sequence )
(seit C++23)


(Anmerkung: Der bereitgestellte Text enthält nur HTML-Tags ohne übersetzbaren Inhalt. Daher bleibt die Ausgabe identisch mit der Eingabe.)
2) Alle während Phase 1 und (bis C++23) Phase 2 durchgeführten Transformationen zwischen dem anfänglichen und dem abschließenden doppelten Anführungszeichen eines Raw-String-Literals werden rückgängig gemacht.
(seit C++11)
3) Whitespace wird transformiert:
  • Jeder Kommentar wird durch ein Leerzeichen ersetzt.
  • Zeilenumbruchzeichen werden beibehalten.
  • Ob jede nichtleere Sequenz von Leerzeichen außer Zeilenumbrüchen beibehalten oder durch ein Leerzeichen ersetzt wird, ist nicht spezifiziert.

Phase 4: Vorverarbeitung

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

Phase 5: Bestimmung gängiger String-Literal-Kodierungen

1) Alle Zeichen in Zeichenliteralen und Zeichenkettenliteralen werden vom Quellzeichensatz in die Kodierung konvertiert (die eine Multibyte-Zeichenkodierung wie UTF-8 sein kann, solange die 96 Zeichen des Basiszeichensatzes Einzelbyte-Darstellungen haben).
2) Escape-Sequenzen und universelle Zeichennamen in Zeichenliteralen und nicht-rohen Zeichenkettenliteralen werden erweitert und in die Literalkodierung konvertiert.

Falls das durch einen universellen Zeichennamen spezifizierte Zeichen nicht als einzelner Codepunkt in der entsprechenden Literalkodierung dargestellt werden kann, ist das Ergebnis implementierungsdefiniert, garantiert jedoch kein Null-(Breit-)Zeichen.

(bis C++23)

Für eine Sequenz von zwei oder mehr benachbarten Zeichenkettenliteral -Tokens wird ein gemeinsames Kodierungspräfix wie hier beschrieben bestimmt. Jedes solche Zeichenkettenliteral-Token wird dann als mit diesem gemeinsamen Kodierungspräfix betrachtet. (Zeichenkonvertierung wird in Phase 3 verschoben)

(seit C++23)

Phase 6: Verkettung von String-Literalen

Benachbarte String-Literale werden verkettet.

Phase 7: Kompilierung

Die Kompilierung findet statt: Jeder Präprozessor-Token wird in einen Token umgewandelt. Die Tokens werden syntaktisch und semantisch analysiert und als eine Translation Unit übersetzt.

Phase 8: Instanziierung von Templates

Jede Übersetzungseinheit wird geprüft, um eine Liste der benötigten Template-Instanziierungen zu erstellen, einschließlich derjenigen, die durch explizite Instanziierungen angefordert werden. Die Definitionen der Templates werden lokalisiert, und die erforderlichen Instanziierungen werden durchgeführt, um Instanziierungseinheiten zu erzeugen.

Phase 9: Linking

Übersetzungseinheiten, Instanziierungseinheiten und Bibliothekskomponenten, die zur Erfüllung externer Referenzen benötigt werden, werden in ein Programmabbild gesammelt, das die für die Ausführung in seiner Ausführungsumgebung benötigten Informationen enthält.

Hinweise

Quelldateien, Übersetzungseinheiten und übersetzte Übersetzungseinheiten müssen nicht unbedingt als Dateien gespeichert werden, noch muss es eine Eins-zu-eins-Entsprechung zwischen diesen Entitäten und einer externen Darstellung geben. Die Beschreibung ist nur konzeptionell und spezifiziert keine bestimmte Implementierung.

Die in Phase 5 durchgeführte Konvertierung kann in einigen Implementierungen durch Befehlszeilenoptionen gesteuert werden: gcc und clang verwenden - finput - charset zur Angabe der Kodierung des Quellzeichensatzes, - fexec - charset und - fwide - exec - charset zur jeweiligen Angabe der gewöhnlichen und breiten Literalkodierungen, während Visual Studio 2015 Update 2 und höher / source - charset und / execution - charset zur jeweiligen Angabe des Quellzeichensatzes und der Literalkodierung verwendet.

(bis C++23)

Einige Compiler implementieren keine Instanziierungseinheiten (auch bekannt als template repositories oder template registries ) und kompilieren einfach jede Template-Instanziierung in Phase 7, speichern den Code in der Objektdatei, wo er implizit oder explizit angefordert wird, und anschließend reduziert der Linker diese kompilierten Instanziierungen in Phase 9 auf eine.

Fehlerberichte

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

DR Angewendet auf Verhalten wie veröffentlicht Korrektes Verhalten
CWG 787 C++98 das Verhalten war undefiniert, wenn eine nicht-leere Quelldatei
am Ende von Phase 2 nicht mit einem Zeilenumbruchzeichen endet
in diesem Fall ein abschließendes Zeilenumbruchzeichen
hinzufügen
CWG 1104 C++98 das alternative Token < : führte dazu, dass std:: vector < :: std:: string >
behandelt wurde als std:: vector [ : std:: string >
eine zusätzliche Lexing-Regel
hinzugefügt, um diesen Fall zu behandeln
CWG 1775 C++11 die Bildung eines universal character name innerhalb eines raw
string literal in Phase 2 führte zu undefiniertem Verhalten
als wohldefiniert festgelegt
CWG 2747 C++98 Phase 2 überprüfte den End-of-File-Splice nach dem Splicing, dies ist unnötig die Überprüfung entfernt
P2621R3 C++98 universal character names durften nicht
durch Line Splicing oder Token Concatenation gebildet werden
erlaubt

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 Übersetzungsphasen [lex.phases]

Siehe auch

C-Dokumentation für Übersetzungsphasen