编译与连接
翻译环境和运行环境
在 ANSI C的任何一种实现中,存在两个不同环境
第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令
第二种是运行环境,用于实际执行代码

翻译环境
翻译环境是由编译和链接两大过程组成的

编译又可以分解成:预处理、编译、汇编三个过程

一个C语言项目中可能有多个.c文件构成,多个.c文件会单独经过编译器,编译处理生
成对应的目标文件,多个目标文件和链接库一起经过链接器处理生成最终的可执
行程序
在 windows 环境下目标文件后缀为.obj, linux 环境下目标文件后缀为.o
预处理
预处理阶段,源文件和头文件会被处理为.i文件
主要处理源文件中 # 开始的预编译指令
将所有 #define 删除,并展开宏定义
处理所有条件编译指令,如:#if、#ifdef、#elif、#else、#endif
处理 #include 预编译指令,将包含的头文件的内容插入到该预编译指令的位置
删除所有注释
添加行号和文件名标识,方便后续编译器生成调试信息
或保留所有的 #pragma 的编译器指令,编译器后续会使用
经过预处理后的.i文件中不再包含宏定义,因为宏已经被展开。并且包含的头文件
都被插入到.i文件
中。所以当我们无法知道宏定义或者头文件是否包含正确的时
候,可以查看预处理后的.i文件来确认。
编译
编译就是将预处理后的文件的进行词法分析、语法分析、语义分析及优化
假如有下列代码
1 | int arr[10] = { 0 }; |
词法分析
将源代码程序输入扫描器,扫描器的任务是简单的进行词法分析,将代码中的字符分割成一系列的记号(关键字、标识符、字面量、特殊字符等)
| 记号 | 类型 | |
|---|---|---|
| int | 关键字 | |
| arr | 标识符 | |
| [ | 左方括号 | |
| 10 | 数字 | |
| ] | 右方括号 | |
| = | 赋值 | |
| { | 左大括号 | |
| 0 | 数字 | |
| } | 右大括号 | |
| arr | 标识符 | |
| [ | 左方括号 | |
| 0 | 数字 | |
| ] | 右方括号 | |
| = | 赋值 | |
| 1 | 数字 |
语法分析
接下来语法分析器对扫描产生的记号进行语法分析从而产生语法树,这些语法树是以表达式为节点的数

语义分析
由语义分析器来完成语义分析,即对表达式的语法层次分析
编译器做的分析是语义的静态分析,通常包含声明和类型的匹配,类型的转换等,这
个阶段会报告错误的语法信息
汇编
汇编器是将汇编代码转转变成机器可执行的指令,每一个汇编语句几乎都对应一
条机器指令。就是根据汇编指令和机器指令的对照表一一的进行翻译,也不做指
令优化。
链接
链接是一个复杂的过程,需要把一堆文件链接在一起生成可执行程序
主要包括:地址和内存分配,符号决议和重定位等这些步骤
解决的是一个项目中多文件、多模块之间的调用问题
例如现在一个C语言项目中有两个文件 add.c 和 native_c.c
1 | // add.c |
1 | // native_c.c |
native_c.c 经过编译器处理生成 native_c.o
add.c 经过编译器处理生成 add.o
在 native_c.c 文件中使用了 add.c 中的 Add 函数
和 num 变量,必须知道 Add 函数 和 num 变量的地址,所以暂时会把调用 Add 的
指令的目标地址和 num 的地址搁置。等到链接的时候由链接器根据引用的符号
Add 在其他模块中查找 Add 的地址,然后将 native_c.c 所有引用 Add 的指令重新
修正,让他们的目标地址称为真正的 Add 函数的地址,变量 num 类似。这个地址修
正的过程称为重定位



