编译和链接
一、预编译
预编译的过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如“include”、"define"等,主要处理规则如下:
- 将所有的“define”删除,并且展开所有的宏定义
- 处理所有条件预编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”.
- 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归的,也就是说被包含的文件可能还包含其他文件。
- 删除所有的注释“//”和“/**/”
- 添加行号和文件名标识,比如#2“hello.c”2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
- 保留所有的#pragma编译器指令,因为编译器需要使用他们。
经过编译的
.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中。
二、编译
编译的过程就是把预处理完的文件进行一些列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件,
array[index] = (index + 4) * (2 + 6)
通过这段代码介绍整个编译过程
1. 语法分析
将源代码的字符序列分割成一些列的记号(Token)。比如上面那行程序,总共包含28个非空字符,经过扫描后产生16个记号。
| 记号 | 类型 |
|---|---|
| array | 标识符 |
| [ | 左方括号 |
| ] | 标识符 |
| = | 赋值 |
| ( | 左圆括号 |
| inedx | 标识符 |
| + | 加号 |
| 4 | 数字 |
| ) | 右圆括号 |
| * | 乘号 |
| ( | 左圆括号 |
词法分析产生的记号一般可以分为如下几类:
- 关键字
- 标识符
- 字面量(数字、字符串等)
- 特殊符号(加号、等号)
语法分析
语法分析器对扫描器产生的记号进行语法分析,从而产生语法树(AST)。简单的说,由语法分析器生成的语法树就是以表达式为节点的树。
语义分析
语法分析仅仅是完成了对表达式的语法层面的分析,但是它并不了解这个语句是否真正有意义。语义分析能分析静态语义(可以在编译器确定的语义),通常包括声明和类型的匹配,类型的转换。
经过语义分析阶段后,整个语法树的表达式都被标识了类型,如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点。
生成中间语言
源码优化器将整个语法树转换成为中间代码,它是语法树的顺序表示。
汇编
将中间代码(汇编代码)转为机器可以执行的指令,生成的文件为.o文件,每个和目标文件生成不同的.o文件。
链接
链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。
链接过程主要包括了地址和空间分配、符号决议和重定位等这些步骤,符号决议有时候也叫符号绑定
最基本的链接过程:每个模块的源代码文件(如.c)文件经过编译器编译成目标文件(一般为.o或.obj)
,目标文件和库(library)一起进行链接
使用链接器,可以直接饮用其他模块的函数和全局变量而无需知道它们的地址,因为链接器在链接的时候,会根据你所引用的符号,自动去相应的模块查找符号地址,然后将模块中所有引用到该符号的指令重新修正。这个重新修正的过程,被叫做
重定位