Phases of translation
C++-Quelldateien werden vom Compiler verarbeitet, um C++-Programme zu erstellen.
Ü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:
- Ordnet jede Quelldatei einer Zeichenfolge zu.
- Konvertiert jede Zeichenfolge in eine Präprozessor-Token-Sequenz, getrennt durch Leerzeichen.
- Konvertiert jedes Präprozessor-Token in ein Token und bildet eine Token-Sequenz.
- 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:
- Header-Namen (wie < iostream > oder "myfile.h" )
|
(seit C++20) |
- Identifikatoren
- Präprozessorzahlen (siehe unten)
- Zeichenliterale , einschließlich benutzerdefinierter Zeichenliterale (seit C++11)
- Zeichenkettenliterale , einschließlich benutzerdefinierter Zeichenkettenliterale (seit C++11)
- Operatoren und Interpunktoren , einschließlich alternativer Tokens
- einzelne Nicht-Leerzeichen-Zeichen, die in keine andere Kategorie passen
-
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) |
|
(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];"
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
#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:
- Bezeichner
- Schlüsselwörter
- Literale
- Operatoren und Interpunktoren (außer Präprozessor-Operatoren)
Ü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.
|
(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).
|
(since C++23) |
Phase 2: Zeilenverknüpfung
Phase 3: Lexing
// The following #include directive can de decomposed into 5 preprocessing tokens: // punctuators (#, < and >) // │ // ┌────────┼────────┐ // │ │ │ #include <iostream> // │ │ // │ └── header name (iostream) // │ // └─────────── identifier (include)
// 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:
|
(seit C++23) |
|
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) |
- 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
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
|