翻译阶段
编译器处理 C++ 源文件时,如同严格按照以下顺序进行各个阶段的处理:
阶段 1
1) 将源文件的各个单独字节(以具体实现所定义的方式)映射为基础源字符集的字符。特别是,操作系统相关的行尾指示符均被替换为换行字符。
2) 可以接受的源文件字符的集合由实现定义。 (C++11 起)任何无法被映射到基础源字符集中的字符的源文件字符均被替换为它的通用字符名(用
\u 或 \U 转义),或使用某种(由实现所定义的)等效处理的方式。
|
(C++23 前) | ||
保证至少支持 UTF-8 代码单元的序列的输入文件(UTF-8 文件)。其他支持的输入文件的种类的集合由实现定义。该集合不为空时,决定文件种类的方式通过由实现定义且以与内容无关的方式决定(包括指定输入文件为 UTF-8 文件,只识别字节序标记无法满足该要求)。
|
(C++23 起) |
阶段 2
阶段 3
(C++20 起) |
- 撇号(',U+0027)
- 引号(",U+0022)
- 基本字符集以外的字符
(C++11 起) |
保留换行符。未指明是否可以将非换行空白字符序列缩减成单个空格字符。
在组成预处理记号而吸收字符时(即不组成注释或其他形式的空白),通用字符名会被识别并被翻译字符集中的指定元素替换,除非正在匹配以下内容中的字符序列: |
(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
阶段 5
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++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 787 | C++98 | 非空源文件在阶段 2 结束时如果不以换行符结尾,那么行为未定义 | 此时在结尾添加一个换行符 |
CWG 1775 | C++11 | 阶段 2 中在原始字符串字面量内组成通用字符名时行为未定义 | 赋予良好定义 |
P2621R2 | C++98 | 不允许通过拼接行或拼接记号来组成通用字符名 | 允许 |
引用
- 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]