编译过程
预编译—>编译—>汇编—>二进制可重定位目标文件
预编译
首先是将源代码相关的头文件,如stdio.h等被预编译器cpp预编译成一个.i文件。
1 | gcc -E hello.c -o hello.i |
预编译过程主要处理那些在源代码中以 #
开始的预编译指令。如:#include 、 #define ,主要规则如下
- 将所有的 “#define” 删除,并且展开所有的宏定义
- 处理所有的条件预编译指令,比如 #if, #ifdef, #elif, #else, #endif
- 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置(注意:这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件)
- 删除所有的注释 // 和 /**/
- 添加行号和文件名标识,便于后面编译器调试用的行号信息和错误警告显示行号
- 保留所有的#pragma编译器指令,因为编译器需要使用它们
经过预编译后.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件已经被插入到.i文件中。所以当我们无法判断宏定义是否正确或头文件是否包含正确时,可以查看预编译后的文件来确定问题。
编译
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生产相应的汇编代码文件,这个过程往往是程序构建过程中的核心部分,也是最复杂的部分
汇编
将汇编代码转变为机器可以执行的指令,每一个汇编指令几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来说比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只需根据汇编指令和机器指令的对照表一一翻译即可。
1 | as hello.s -o hello.o |
1 | gcc -c hello.s -o hello.o |
链接
编译完成的所有.o文件 + 静态库文件
步骤一:所有.o文件段的合并,符号表合并后,进行符号解析
步骤二:符号的重定位(重定向)。