翻译阶段

来自cppreference.com
< cpp‎ | language

编译器处理 C++ 源文件时,如同严格按照以下顺序进行各个阶段的处理:

阶段 1

1) (以实现定义方式)将源文件的各个单独字节映射为基础源字符集 (C++23 前)翻译字符集 (C++23 前)的字符。特别是,操作系统相关的行尾指示符均被替换为换行字符。
2) 可以接受的源文件字符的集合由实现定义。 (C++11 起)任何无法被映射到基础源字符集中的字符的源文件字符均被替换为其通用字符名(用 \u\U 转义),或某种被等价处理的由实现定义的形式。 (C++23 前)
3) 将各个三标符序列替换为其对应的单字符表示。
(C++17 前)

阶段 2

1) 当反斜杠出现于行尾(其后紧跟零或多个除换行符外的空白符,再紧跟 (C++23 起)换行符)时,删除这些字符并将两个物理源码行组合成一个逻辑源码行。这是单趟操作:如果有一行以两个反斜杠结束且后随一个空行,这三行不会合为一行。如果在这个阶段组成了通用字符名,那么行为未定义。
2) 如果在此步骤后非空源文件不以换行符结束(无论是原本就没有换行,还是以反斜杠结束),那么其行为未定义 (C++11 前)在最后添加一个换行符 (C++11 起)

阶段 3

1) 将源文件分解为注释,空白字符(空格、水平制表、换行、垂直制表和换页)的序列,和下列各种预处理记号
a) 头名,如 <iostream>"myfile.h"
c) 预处理数字
d) 字符字符串字面量,包括用户定义 (C++11 起)
e) 运算符和标点(包括代用记号),如 +<<=<%##and
f) 不属于任何其他类别的单独非空白字符
2) 撤回在任何原始字符串字面量的首尾双引号之间在阶段 1 和 2 期间进行的所有变换。
(C++11 起)
3) 以一个空格字符替换每段注释。

保留换行符。未指明是否可以将非换行空白字符序列缩减成单个空格字符。

在组成预处理记号而吸收字符时(即不组成注释或其他形式的空白),通用字符名会被识别并被翻译字符集中的指定元素替换,除非正在匹配以下内容中的字符序列:

a) 字符字面量c字符序列
b) 字符串字面量s字符序列r字符序列),但不包括分隔符(d字符序列
c) 要包含的文件名h字符序列q字符序列
(C++23 起)

如果一个给定字符前的输入已被解析为预处理记号,下一个预处理记号通常会由能构成预处理记号的最长字符序列构成,即使这样处理会导致后续分析失败。这常被称为最大吞噬

int foo = 1;
int bar = 0xE+foo;   // 错误:非法的预处理数字 0xE+foo
int baz = 0xE + foo; // OK
 
int quux = bar+++++baz; // 错误:bar++ ++ +baz,而非 bar++ + ++baz。

最大吞噬规则 (C++11 前)有以下例外:

  • 如果以下一个字符开头的字符序列可作为原始字符串字面量的前缀和起始双引号,那么下个预处理记号应当为原始字符串字面量。该字面量由匹配原始字符串模式的最短字符序列组成。
#define R "x"
const char* s = R"y"; // 非良构的原始字符串字面量,而非 "x" "y"
const char* s2 = R"(a)" "b)"; // 原始字符串字面量后随普通字符串字面量
  • 如果接下来的三个字符是 <::且后继字符不是 : 或者 >,那么把 < 自身当做预处理记号(而非代用记号 <: 的首字符)。
struct Foo { static const int v = 1; };
std::vector<::Foo> x; // OK,<: 未被当作 [ 的代用记号
extern int y<::>;     // OK,同 extern int y[]。
int z<:::Foo::value:>; // OK,int z[::Foo::value];
(C++11 起)
  • 头文件名预处理记号只会在 #include 指令中形成。
std::vector<int> x; // OK,<int> 不是头文件名

阶段 4

1) 执行预处理器如果记号拼接产生通用字符名,那么行为未定义。 (C++23 前)
2) #include 指令所引入的每个文件都经历阶段 1 到 4 的处理,递归执行。
3) 此阶段结束时,所有预处理器指令都应从源(代码)移除。

阶段 5

1)字符字面量字符串字面量中的所有字符从源字符集转换到执行字符集(可以是 UTF-8 这样的多字节字符集,只要基础源字符集的 96 个字符都拥有单字节表示即可)。
2) 将字符字面量和非原始字符串字面量中的转义序列和通用字符名展开,并转换到执行字符集。 若某个通用字符名所指定的字符不是执行字符集的成员,则结果是由实现定义的,但保证不是空(宽)字符。

注意:某些实现能以命令行选项控制此阶段所进行的转换:gcc 和 clang 用 -finput-charset 指定源字符集的编码,用 -fexec-charset-fwide-exec-charset 指定无编码前缀的 (C++11 起)字符串和字符字面量中的执行字符集的编码,而 Visual Studio 2015 Update 2 及之后版本分别用 /source-charset/execution-charset 指定源字符集和执行字符集。

(C++23 前)

对于每个含有多个相邻字符串字面量记号的序列,都会有一个以此规则指定的共同编码前缀。其中每个字符串字面量记号都会被视为拥有该共同编码前缀。 (字符转换改为在阶段 3 执行)

(C++23 起)

阶段 6

拼接相邻的字符串字面量

阶段 7

进行编译:将各个预处理记号转换成记号。将所有记号当作一个翻译单元进行语法和语义分析并进行翻译。

阶段 8

检验每个翻译单元,产生所要求的模板实例化的列表,其中包括显式实例化所要求的实例化。定位模板定义,并进行所要求的实例化,以产生实例化单元

阶段 9

将翻译单元、实例化单元和为满足外部引用所需的库组件汇集成一个程序映像,它含有在其执行环境中执行所需的信息。

注解

某些编译器不实现实例化单元(又称为模板仓库模板注册表),而是简单地在阶段 7 编译每个模板实例化,存储代码于其所显式或隐式要求的对象文件中,然后由连接器于阶段 9 将这些编译后的实例化缩减到一个。

引用

  • C++20 标准(ISO/IEC 14882:2020):
  • 5.2 Phases of translation [lex.phases]
  • C++17 标准(ISO/IEC 14882:2017):
  • 5.2 Phases of translation [lex.phases]
  • C++14 标准(ISO/IEC 14882:2014):
  • 2.2 Phases of translation [lex.phases]
  • C++11 标准(ISO/IEC 14882:2011):
  • 2.2 Phases of translation [lex.phases]
  • C++03 标准(ISO/IEC 14882:2003):
  • 2.1 Phases of translation [lex.phases]
  • C++98 标准(ISO/IEC 14882:1998):
  • 2.1 Phases of translation [lex.phases]

参阅