现在流行的集成开发环境(IDE)使我们没有去重视编译和链接的细节,编译链接步骤一步完成,通常将这种编译和链接合并到一起的过程称为构建。二编译链接可以分为四个步骤:预处理,编译,汇编,链接。
预编译
预编译会将.h文件被预编译器cpp预编译成.i文件,而预编译的文件扩展名是.ii,预编译的过程相当于如下指令。
$gcc -E hello.c -o hello.i
或者
$cpp hello.c > hello.i
预编译过程主要处理源代码文件中以“#”开始的预编译指令,处理规则如下:
- 将所有的“#define”删除,并且展开所有的宏定义。
- 处理所有条件预编译指令,比如“#if”,“#ifdef”,“#elif”,“#else”,“#endif”。
- 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
- 删除注释。
- 添加行号和文件名标识,方便报错
- 保留所有的#pragma编译器指令,因为编译器需要使用他们。
编译
编译包含一列列操作:词法分析,语法分析,语义分析,优化后生产相应的汇编代码文件。上述过程也可以用下面的语句一并执行:
$gcc -S hello.i -o hello.s
预编译和编译也可以一起通过一条语句执行:
$gcc -S hello.c -o hello.s
词法分析
首先源代码程序被输入到扫描器,扫描器简单的进行词法分析,运用一种类似于有限状态机的算法可以很轻松地将源代码的字符序列分割成一系列的记号。
词法分析产生的记号可以分为以下几类:关键字,标识符,字面量(包含数字,字符串等)和特殊符号(如加号,等号)。
词法扫描有一个lex的程序。
语法分析
语法分析器将对扫描器产生的记号进行语法分析,从而产生语法树,整个分析过程采用了上下文无关语法的分析手段。简单地说,由语法分析器生成的语法树就是以表达式为节点的树。
语法扫描有一个工具yacc(Yet Another Compiler Compiler)。
语义分析
语义分析由语义分析器来完成。编译器所有分析的语义是静态语义,所谓静态语义是指在编译器可以确定的语义,与之对应的动态语义只有在运行期才能确定的语义。
静态语义通常包括声明和类型的匹配,类型的转换。比如当一个浮点型的表达式赋给一个整形的表达式时,其中隐含了一个浮点型到整形转换的过程,语义分析过程中需要完成这个步骤。又比如将一个浮点型赋值给一个指针的时候,语义分析程序会发现这个类型不匹配,编译器会报错。而动态语义一般指在运行期出现的语义相关的问题,又比如将0作为除数是一个运行期语义错误。
中间语言生成
中间代码比如1+2这个就会优化成3.其实直接在语法树上作优化比较困难,所以源代码优化器往往将整个语法树转换成中间代码。
目标代码生成与优化
源代码级优化器产生中间代码标志着下面的过程都属于编译器后端。编译器后端主要包括代码生成器和目标代码优化器。
链接
链接过程主要包括了地址和空间分配,符号决议和重定位等这些步骤。
最基本的静态链接如图所示,每个模块的源代码文件经过编译成目标文件,目标文件和库一起链接形成最终可执行文件。
使用链接器,你可以直接引用其他模块的函数和全局变量而无需知道他们的地址。