文章整理自: http://c.biancheng.net/c/150/
从源代码生成可执行文件可以分为四个步骤,分别是预处理(Preprocessing)
、编译(Compilation)
、汇编(Assembly)
和链接(Linking)
。
下图是 GCC 生成 a.out 的过程:
1. 预处理(Preprocessing)
预处理过程主要是处理那些源文件和头文件中以#
开头的命令,比如 #include
、#define
、#ifdef
等。预处理的规则一般如下:
- 将所有的
#define
删除,并展开所有的宏定义。 - 处理所有条件编译命令,比如
#if
、#ifdef
、#elif
、#else
、#endif
等。 - 处理
#include
命令,将被包含文件的内容插入到该命令所在的位置,这与复制粘贴的效果一样。注意,这个过程是递归进行的,也就是说被包含的文件可能还会包含其他的文件。 - 删除所有的注释
//
和/* ... */
。 - 添加行号和文件名标识,便于在调试和出错时给出具体的代码位置。
- 保留所有的
#pragma
命令,因为编译器需要使用它们。
预处理的结果是生成.i
文件。.i
文件也是包含C语言代码的源文件,只不过所有的宏已经被展开,所有包含的文件已经被插入到当前文件中。当你无法判断宏定义是否正确,或者文件包含是否有效时,可以查看.i
文件来确定问题。
在 GCC 中,可以通过下面的命令生成.i
文件:
$gcc -E demo.c -o demo.i
-E
表示只进行预编译。
2. 编译(Compilation)
编译就是把预处理完的文件进行一些列的词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件,编译的结果是产生包含汇编代码的.s
文件
在 GCC 中,可以使用下面的命令生成.s文件:
$gcc -S demo.i -o demo.s
或者
$gcc -S demo.c -o demo.s
3. 汇编(Assembly)
汇编的过程就是将汇编代码转换成可以执行的机器指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令。
汇编过程相对于编译来说比较简单,没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编语句和机器指令的对照表一一翻译就可以了。
汇编的结果是产生后缀为.o
的目标文件。
4. 链接(Linking)
目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,程序不能执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。