名稱空間
變體
操作

翻譯階段

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

C++ 原始檔由編譯器處理以生成 C++ 程式。

目錄

[編輯] 翻譯過程

C++ 程式的文字儲存在稱為 原始檔 的單元中。

C++ 原始檔經過 翻譯 成為一個 翻譯單元,包括以下步驟

  1. 將每個原始檔對映到字元序列。
  2. 將每個字元序列轉換為預處理記號序列,由空白分隔。
  3. 將每個預處理記號轉換為記號,形成記號序列。
  4. 將每個記號序列轉換為翻譯單元。

C++ 程式可以由翻譯後的翻譯單元組成。翻譯單元可以單獨翻譯,然後連結以生成可執行程式。

上述過程可以組織成 9 個翻譯階段

[編輯] 預處理記號

預處理記號 是翻譯階段 3 到 6 中語言的最小詞法元素。

預處理記號的類別是

(C++20 起)
如果匹配此類別的字元是以下之一,則程式格式錯誤
  • 撇號(',U+0027),
  • 引號(",U+0022),或
  • 不在基本字元集中的字元。

[編輯] 預處理數字

預處理數字的預處理記號集是整型字面量浮點型字面量的記號集的並集的超集

.(可選) digit pp-continue-seq (可選)
digit - 0-9 中的一個數字
pp-continue-seq - 一個 pp-continue 序列

每個 pp-continue 是以下之一

identifier-continue (1)
exp-char sign-char (2)
. (3)
digit (4) (C++14 起)
nondigit (5) (C++14 起)
identifier-continue - 有效識別符號的任何非首字元
exp-char - 以下之一 P, p,(C++11 起) Ee
sign-char - +- 之一
digit - 0-9 中的一個數字
nondigit - 拉丁字母 A/a-Z/z 和下劃線之一

預處理數字沒有型別或值;它在成功轉換為整型/浮點型字面量記號後獲得二者。

[編輯] 空白

空白註釋、空白字元或兩者組成。

以下字元是空白字元

  • 字元製表符 (U+0009)
  • 換行符 / 新行字元 (U+000A)
  • 行製表符 (U+000B)
  • 換頁符 (U+000C)
  • 空格 (U+0020)

空白通常用於分隔預處理記號,但以下情況除外

  • 在標頭檔案名稱、字元字面量和字串字面量中,它不是分隔符。
  • 包含換行符的空白分隔的預處理記號不能形成預處理指令
#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)

[編輯] 最大匹配

如果輸入已解析成預處理記號直到給定字元,則下一個預處理記號通常被認為是能夠構成預處理記號的最長字元序列,即使這會導致後續分析失敗。這通常被稱為最大匹配

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

換句話說,最大匹配規則有利於多字元運算子和標點符號

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”

最大匹配規則有以下例外

  • 標頭檔案名稱預處理記號僅在以下情況下形成
  • #include 指令中的 include 預處理記號之後
(C++17 起)
  • import 指令中的 import 預處理記號之後
(C++20 起)
std::vector<int> x; // OK, “int” is not a header name
  • 如果接下來的三個字元是 <::,並且隨後的字元既不是 : 也不是 >,則 < 本身被視為一個預處理記號,而不是備用記號 <: 的第一個字元。
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];”
  • 如果接下來的兩個字元是 >>,並且其中一個 > 字元可以完成一個模板識別符號,則該字元被視為一個獨立的預處理記號,而不是預處理記號 >> 的一部分。
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
  • 如果下一個字元開始的字元序列可以是原始字串字面量的字首和初始雙引號,則下一個預處理記號是原始字串字面量。該字面量由最短的匹配原始字串模式的字元序列組成。
#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
(C++11 起)

[編輯] 記號

記號 是翻譯階段 7 中語言的最小詞法元素。

記號的類別是

[編輯] 翻譯階段

翻譯按從階段 1 到階段 9 的順序彷彿執行。實現的行為彷彿這些獨立的階段發生,儘管在實踐中不同的階段可以合併在一起。

[編輯] 階段 1:對映源字元

1) 原始碼檔案的單個位元組被(以實現定義的方式)對映到基本源字元集中的字元。特別是,與作業系統相關的行尾指示符被換行符替換。
2) 接受的原始檔字元集是實現定義的(C++11 起)。任何不能對映到基本源字元集中字元的原始檔字元都被其通用字元名稱(用 \u\U 轉義)或某種等效處理的實現定義形式替換。
3) 三字元序列被替換為相應的單字元表示。
(C++17 前)
(直至 C++23)

保證支援作為 UTF-8 碼元序列(UTF-8 檔案)的輸入檔案。其他支援的輸入檔案種類集是實現定義的。如果該集非空,則輸入檔案的種類以實現定義的方式確定,包括一種將輸入檔案指定為 UTF-8 檔案的方法,獨立於其內容(識別字節順序標記不足)。

  • 如果輸入檔案被確定為 UTF-8 檔案,則它應是一個格式良好的 UTF-8 碼元序列,並被解碼以生成 Unicode 標量值序列。然後,透過將每個 Unicode 標量值對映到相應的翻譯字元集元素,形成一個翻譯字元集元素的序列。在結果序列中,輸入序列中由回車符(U+000D)後跟換行符(U+000A)組成的每對字元,以及未緊跟換行符(U+000A)的回車符(U+000D),都被單個換行符替換。
  • 對於實現支援的任何其他型別的輸入檔案,字元以(實現定義的方式)對映到翻譯字元集元素的序列。特別是,與作業系統相關的行尾指示符被換行符替換。
(C++23 起)

[編輯] 階段 2:拼接行

1) 如果第一個翻譯字元是位元組順序標記 (U+FEFF),則將其刪除。(C++23 起)當反斜槓(\)出現在行尾時(緊跟零個或更多非換行符的空白字元,然後是(C++23 起)換行符),這些字元被刪除,將兩個物理源行合併為一個邏輯源行。這是一個單次操作;以兩個反斜槓後跟空行結尾的行不會將三行合併為一行。
2) 如果非空原始檔在此步驟之後(此時行尾反斜槓不再是拼接符)沒有以換行符結尾,則新增一個終止換行符。

[編輯] 階段 3:詞法分析

1) 原始檔被分解為預處理記號空白
// 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
當原始檔中的字元被消耗以形成下一個預處理記號時(即,不作為註釋或其他形式空白的一部分被消耗),通用字元名稱被識別並替換為翻譯字元集中指定的元素,除非匹配以下預處理記號之一中的字元序列
  • 字元字面量(c-char-sequence
  • 字串字面量(s-char-sequencer-char-sequence),不包括分隔符(d-char-sequence
  • 標頭檔案名稱(h-char-sequenceq-char-sequence
(C++23 起)


2) 任何在階段 1 和(C++23 前)階段 2 期間在任何原始字串字面量的初始和最終雙引號之間執行的轉換都將恢復。
(C++11 起)
3) 空白被轉換
  • 每個註釋被一個空格字元替換。
  • 換行符被保留。
  • 除換行符以外的每個非空空白字元序列是保留還是替換為一個空格字元是未指定的。

[編輯] 階段 4:預處理

1) 預處理器執行。
2) 每個使用 #include 指令引入的檔案都遞迴地經過階段 1 到階段 4。
3) 在此階段結束時,所有預處理指令都從原始檔中移除。

[編輯] 階段 5:確定通用字串字面量編碼

1) 字元字面量字串字面量中的所有字元都從源字元集轉換為編碼(它可以是多位元組字元編碼,例如 UTF-8,只要基本字元集的 96 個字元具有單位元組表示)。
2) 字元字面量和非原始字串字面量中的轉義序列和通用字元名稱被擴充套件並轉換為字面量編碼。

如果通用字元名稱指定的字元不能編碼為相應字面量編碼中的單個碼點,則結果是實現定義的,但保證不是空(寬)字元。

(直至 C++23)

對於兩個或更多相鄰的字串字面量記號序列,如此處所述確定公共編碼字首。然後,每個此類字串字面量記號都被視為具有該公共編碼字首。(字元轉換移至階段 3)

(C++23 起)

[編輯] 階段 6:連線字串字面量

相鄰的字串字面量被連線。

[編輯] 階段 7:編譯

進行編譯:每個預處理記號被轉換為一個記號。這些記號經過語法和語義分析,並作為翻譯單元進行翻譯。

[編輯] 階段 8:例項化模板

檢查每個翻譯單元以生成所需模板例項化的列表,包括由顯式例項化請求的那些。定位模板定義,並執行所需的例項化以生成例項化單元

[編輯] 階段 9:連結

為滿足外部引用所需的翻譯單元、例項化單元和庫元件被收集到一個程式映像中,該映像包含在執行環境中執行所需的資訊。

[編輯] 注意

原始檔、翻譯單元和翻譯後的翻譯單元不一定以檔案形式儲存,這些實體與任何外部表示之間也不存在一對一的對應關係。此描述僅是概念性的,不指定任何特定的實現。

階段 5 執行的轉換在某些實現中可以透過命令列選項控制:gcc 和 clang 使用 -finput-charset 指定源字元集的編碼,-fexec-charset-fwide-exec-charset 分別指定普通和寬字面量編碼,而 Visual Studio 2015 Update 2 及更高版本使用 /source-charset/execution-charset 分別指定源字元集和字面量編碼。

(直至 C++23)

一些編譯器不實現例項化單元(也稱為模板倉庫模板登錄檔),而只是在階段 7 編譯每個模板例項化,將程式碼儲存在隱式或顯式請求它的目標檔案中,然後在階段 9 連結器將這些已編譯的例項化摺疊為一個。

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 787 C++98 如果非空原始檔在階段 2 結束時沒有
以換行符結尾,則行為未定義
在此情況下新增一個終止換行符
字元
CWG 1104 C++98 備用記號 <: 導致 std::vector<::std::string>
被視為 std::vector[:std::string>
添加了一個額外的詞法分析
規則來處理這種情況
CWG 1775 C++11 在階段 2 中在原始字串字面量內形成通用字元名稱
導致未定義行為
已明確定義
CWG 2747 C++98 階段 2 在拼接後檢查檔案尾拼接,這是不必要的 移除了檢查
P2621R3 C++98 通用字元名稱不允許透過行拼接或記號連線
形成
允許

[編輯] 參考

  • C++23 標準 (ISO/IEC 14882:2024)
  • 5.2 翻譯階段 [lex.phases]
  • C++20 標準 (ISO/IEC 14882:2020)
  • 5.2 翻譯階段 [lex.phases]
  • C++17 標準 (ISO/IEC 14882:2017)
  • 5.2 翻譯階段 [lex.phases]
  • C++14 標準 (ISO/IEC 14882:2014)
  • 2.2 翻譯階段 [lex.phases]
  • C++11 標準 (ISO/IEC 14882:2011)
  • 2.2 翻譯階段 [lex.phases]
  • C++03 標準 (ISO/IEC 14882:2003)
  • 2.1 翻譯階段 [lex.phases]
  • C++98 標準 (ISO/IEC 14882:1998)
  • 2.1 翻譯階段 [lex.phases]

[編輯] 另見

C 文件 關於 翻譯階段