Namespaces
Variants

Phases of translation

From cppreference.net

C源文件由编译器进行处理, 如同 以下阶段严格按照此顺序发生。实际实现可以合并这些操作或采用不同处理方式,只要行为保持一致即可。

目录

阶段 1

1) 源代码文件(通常是采用UTF-8等多字节编码的文本文件)的各个字节,会以实现定义的方式映射到 源字符集 的字符。特别地,操作系统相关的行结束指示符会被换行符替代。
源字符集 是一个多字节字符集,它包含作为单字节子集的 基本源字符集 ,该子集由以下96个字符组成:
a) 5个空白字符(空格、水平制表符、垂直制表符、换页符、换行符)
b) '0' '9' 的10个数字字符
c) 'a' 'z' 以及从 'A' 'Z' 的52个字母
d) 29个标点字符: _ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '
2) 三字符序列 被替换为对应的单字符表示。 (C23前)

阶段 2

1) 当反斜杠出现在行尾(紧接换行符之前)时,反斜杠和换行符都会被删除,从而将两个物理源代码行合并为一个逻辑源代码行。这是一个单次处理操作:以两个反斜杠结尾后接空行的行不会将三行合并为一行。
#include <stdio.h>
#define PUTS p\
u\
t\
s
/* 行拼接发生在阶段2,而宏
 * 在阶段3被词法分析并在阶段4展开,
 * 因此上述代码等价于 #define PUTS puts
 */
int main(void)
{
 /* 使用行拼接调用 puts */ PUT\
S\
("Output ends here\\
0Not printed" /* 行拼接后,剩余的反斜杠
               * 转义了0,导致字符串提前结束
               */
);
}
2) 若在此步骤后非空源文件未以换行符结尾(无论其最初没有换行符,还是以反斜杠结尾),则行为未定义。

阶段 3

1) 源文件被分解为 注释 、空白字符序列(空格、水平制表符、换行符、垂直制表符和换页符),以及以下 预处理记号
a) 头文件名称: < stdio. h > "myfile.h"
c) 预处理数字,包括 整型常量 浮点常量 ,但也包含一些无效标记,例如 1 .. E + 3. foo 0JBK
e) 运算符和标点符号,例如 + <<= < % ##
f) 不属于任何其他类别的单个非空白字符
2) 每个注释将被替换为一个空格字符
3) 换行符会被保留,非换行空白字符序列是否会被折叠为单个空格字符由实现定义。

如果输入已解析到给定字符处的前处理令牌,通常会将下一个前处理令牌视为能构成前处理令牌的最长字符序列,即使这会导致后续分析失败。这通常被称为 maximal munch 原则。

int foo = 1;
// int bar = 0xE+foo; // 错误:无效的预处理数字 0xE+foo
int bar = 0xE/*注释扩展为空格*/+foo; // 正确:0xE + foo
int baz = 0xE + foo; // 正确:0xE + foo
int pub = bar+++baz; // 正确:bar++ + baz
int ham = bar++-++baz; // 正确:bar++ - ++baz
// int qux = bar+++++baz; // 错误:bar++ ++ +baz,而非 bar++ + ++baz
int qux = bar+++/*分隔注释*/++baz; // 正确:bar++ + ++baz

最大吞食规则唯一的例外是:

#define MACRO_1 1
#define MACRO_2 2
#define MACRO_3 3
#define MACRO_EXPR (MACRO_1 <MACRO_2> MACRO_3) // 正常:<MACRO_2> 不是头文件名

阶段 4

1) 预处理器 被执行。
2) 每个通过 #include 指令引入的文件都会递归地经历阶段 1 至阶段 4。
3) 在此阶段结束时,所有预处理器指令将从源代码中移除。

阶段 5

1) 所有 转义序列 字符常量 字符串字面量 中的字符都会从 源字符集 转换为 执行字符集 (可以是多字节字符集,如UTF-8,只要阶段1所列 基本源字符集 中的全部96个字符具有单字节表示形式)。若转义序列指定的字符不属于执行字符集,其结果由实现定义,但保证不会是空(宽)字符。

注意:此阶段执行的转换在某些实现中可通过命令行选项控制:gcc 和 clang 使用 - finput - charset 指定源字符集的编码,使用 - fexec - charset - fwide - exec - charset 分别指定执行字符集在字符串字面量和字符常量中的编码 (无编码前缀的情况) (C11 起)

阶段 6

相邻的 字符串字面量 会被连接。

阶段 7

编译过程:对标记进行语法和语义分析,并将其作为翻译单元进行翻译。

阶段 8

链接过程:将满足外部引用所需的翻译单元和库组件收集到程序映像中,该映像包含在其执行环境(操作系统)中执行所需的信息。

参考文献

  • C23 标准 (ISO/IEC 9899:2024):
  • 5.1.1.2 翻译阶段 (p: TBD)
  • 5.2.1 字符集 (p: TBD)
  • 6.4 词法元素 (p: TBD)
  • C17 标准 (ISO/IEC 9899:2018):
  • 5.1.1.2 翻译阶段 (页: 9-10)
  • 5.2.1 字符集 (页: 17)
  • 6.4 词法元素 (页: 41-54)
  • C11 标准 (ISO/IEC 9899:2011):
  • 5.1.1.2 翻译阶段 (p: 10-11)
  • 5.2.1 字符集 (p: 22-24)
  • 6.4 词法元素 (p: 57-75)
  • C99标准(ISO/IEC 9899:1999):
  • 5.1.1.2 翻译阶段(页码:9-10)
  • 5.2.1 字符集(页码:17-19)
  • 6.4 词法元素(页码:49-66)
  • C89/C90 标准 (ISO/IEC 9899:1990):
  • 2.1.1.2 翻译阶段
  • 2.2.1 字符集
  • 3.1 词法元素

参见

C++ documentation for Phases of translation