C语言翻译阶段详解与编译过程解析
时间:2026-08-20 | 作者:实验室老王 | 阅读:0C语言标准把从源文件到可执行文件的处理过程分为多个阶段,称为翻译阶段。
翻译阶段是个概念模型。编译器实现时,可能会进行优化,或把多个阶段同时处理。
只要保证最终结果与依次执行翻译阶段一致即可。
阶段1
把源文件中的所有字符映射为源字符集中的对应字符,并且把行尾指示符转换为换行符
LF。
源字符集的含义
源字符集这一概念是由C语言标准所提出的。它并未明确规定具体是哪种字符集,只是表明其必须涵盖基本字符集。
基本字符集同样是C语言标准所创造的一个概念。它包含:
- 52个英文大小写字母
- 10个数字
- 29个英文标点符号
- 5个空白字符
这也就是C语言代码所必需的最小字符集。
在基本字符集中,所有字符都必须被编码为1个字节。
如何理解这个概念
其实关于字符集和编码,C语言标准也没讲明白,甚至左右脑互搏。
一边定义源文件使用的字符集就是源字符集,一边又要从源文件映射到源字符集。
之后会专门写一篇文章讲C语言的字符集和编码。目前不用纠结这个概念,关注编译器是怎么处理的就好。
gcc的处理方式
gcc会根据locale确定源文件的编码。如果无法从locale确定,则默认编码是UTF-8。
也可以通过参数-finput-charset指定源文件编码。
gcc的源字符集就是UTF-8编码。
如果经过上述步骤确定的源文件编码不是UTF-8,则会将源文件从该编码转换为UTF-8编码。
这个阶段还有一个操作是替换三字符序列。
不过该特性的使用场景极其罕见,并且已经在C23被移除,所以就不介绍了。
阶段2
如果某一行以反斜杠
结尾,则删除反斜杠和换行符,从而把两行合并成一行。
该操作可以通过两两合并,把多行合并成一行,如下所示:
a
b
c
处理后:
abc
不会重复扫描
该操作不会重复扫描,如下所示:
a\
b
只有第二个会被处理。
尽管处理之后第一个成为了该行最后一个字符,也不会折返回来处理第一个。
这个阶段还要求处理后的源文件必须以换行符结尾。
但几乎所有编译器实现都不做此限制。
阶段3
源文件被分解为预处理标记、空白字符序列和注释。
每条注释都被替换为1个空格。
所有换行符都如数保留,其他空白字符序列由实现决定是否合并成1个空格。
结束位置要求
源文件不能以不完全的预处理标记或注释结尾,比如:
/* comment
缺少*/,编译会报错。
gcc的处理方式
gcc会保留缩进空白字符,删除所有尾随空白字符,并且合并非空白字符之间的空白字符,如下所示:
a b
c d
处理后:
a b
c d
阶段4
执行预处理。
所有通过
#include指令包含进来的文件都递归执行阶段1-4。删除所有预处理指令。
预处理是个非常复杂的过程。
之后会专门写一篇文章来讲。
阶段5
没有编码前缀的字符常量和字符串字面量中的字符和转义序列从源字符集转换为执行字符集。
执行字符集是什么
执行字符集也是C语言标准发明的一个概念。它也包含一个基本字符集,要求跟源字符集的基本字符集一样,额外多了几个控制字符。
编码前缀指的是u8、u、U、L。
这些可以加在字符常量和字符串字面量之前,为其指定运行时编码。
这些字符在阶段7进行处理。
哪些内容会转换
只有通用转义序列和Unicode转义序列才会转换为执行字符集,比如n和u4e00。
八进制和十六进制转义序列会转换为其对应的单字节数据,比如x33。
为什么只转换这两类内容
执行字符集决定字符常量和字符串字面量在程序运行时的编码,从而影响程序输出的编码。
因此该阶段只转换这两者,而不是整个源文件。
因为这两者会作为数据在程序执行时使用,而其他部分只用于编译。
运行时影响
只有当执行字符集与终端的编码相同时,源代码中的字符数据才能被正确显示。
然而执行字符集必须在编译时确定,此时不可能知道运行时的终端编码。
所以依赖执行字符集的C语言程序,只能在特定的单一编码环境中正确运行。
C语言的国际化一直非常糟糕。
即使后面推出了宽字符和Unicode支持,也没有改善太多。
gcc的默认行为
gcc的默认执行字符集是UTF-8,可以通过参数-fexec-charset指定执行字符集。
gcc的执行字符集并不受C语言标准的约束。
举个例子,UTF-16不存在单字节编码的字符,这并不符合基本字符集的要求,然而gcc依旧能够将其作为执行字符集。
但这样做会引发一些问题。
在阶段7,字符串字面量会被转化为数组。无论执行字符集是什么,没有编码前缀的字符串字面量都会在末尾添加一个单字节空字符。
由于它并非合法的宽字符串,使用宽字符函数进行处理时就会导致内存越界。
而且执行字符集将任何值为0的字节都视为空字符,不论这个字节在编码中的位置如何。
所以用普通函数处理时,会导致提前结束。
阶段6
合并相邻的字符串字面量。
char *s = "abc"
"de";
等价于:
char *s = "abcde";
阶段7
空白字符被忽略。预处理标记转换为翻译标记,所有翻译标记作为一个翻译单元进行语法和语义分析,翻译成目标文件。
这一步就是我们常说的编译。
阶段8
解析外部对象和函数引用,链接库以满足外部引用。所有目标文件整合成单个可执行文件。
这一步就是我们常说的链接。
C语言不具备自动查找必要库的能力。
编译器会默认链接某些库,而其他库都需要通过编译器参数手动链接。
翻译阶段的意义
翻译阶段明确了广义的编译过程中每种操作在逻辑上的先后顺序,从而尽量避免歧义。
同时,它也从语法无法描述的角度对代码进行规范。
示例1:多行预处理指令
比如下面的代码:
#define SWAPINT(A, B) ((A)^=(B),
(B)^=(A),
(A)^=(B))
因为处理行尾在预处理之前,所以就实现了语法上不允许的多行预处理指令。
示例2:反斜杠影响行注释
而下面的代码:
// this is a comment
printf("hello, worldn");
同样因为处理行尾在处理注释之前,所以一个意外的字符就能让行注释影响到代码。
示例3:注释不影响字符串合并
再看下面的代码:
char *s = "abc" // first line
"de";
因为处理注释在合并字符串字面量之前,所以这两个字符串字面量依然是相邻的,可以合并。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- 【C语言基础】:深入理解指针(三)
- 时间:2026-08-27
-
- C语言基础:字符与字符串函数详解及模拟实现
- 时间:2026-08-27
-
- C语言进阶:深入解析字符串、结构体与typedef用法
- 时间:2026-08-27
-
- LibCoroutine:细粒度C语言协程库开发解析
- 时间:2026-08-27
-
- C语言分支与循环入门:if、switch 和三种循环该怎么用
- 时间:2026-08-25
-
- C 语言字符指针入门:搞清地址、字符串常量与数组的区别
- 时间:2026-08-24
-
- C语言指针入门:从地址、数组到函数调用一次讲清
- 时间:2026-08-24
-
- C 语言自定义数据类型梳理:枚举、结构体、共用体与 typedef
- 时间:2026-08-24
精选合集
更多大家都在玩
大家都在看
更多-
- 以下哪种食材被称为“地下苹果” 蚂蚁庄园今日答案9.18
- 时间:2026-09-17
-
- 蚂蚁庄园今天答题答案2026年9月18日
- 时间:2026-09-17
-
- 蚂蚁庄园答题今日答案2026年9月18日
- 时间:2026-09-17
-
- 蚂蚁庄园小课堂2026年9月18日最新题目答案
- 时间:2026-09-17
-
- 小鸡答题今天的答案是什么2026年9月18日
- 时间:2026-09-17
-
- 蚂蚁庄园每日答题答案2026年9月18日
- 时间:2026-09-17
-
- 蔬菜洗完掉色,说明是被染色了,是真的吗 蚂蚁庄园今日答案9月18日
- 时间:2026-09-17
-
- 满襟蜡绘花纹巧染就花纹当绣裳说的是哪种传统技艺 蚂蚁新村今日答案2026.9.17
- 时间:2026-09-17
