GCC 编译文件生成可执行文件的过程分析
学硬件的同学第一件事肯定是GPIO点亮LED灯,而学软件的同学,大部分做的第一件事就是Hello world!
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World!\n");
return 0;
}
相信上述代码很多同学闭着眼睛都能敲出来,然后使用下面的命令编译执行一气呵成!
很多时候我们都没有仔细研究gcc main.c -o main这个命令到底做了什么,今天我们就来分析下。
事实上,上述过程基本可以分为4个步骤,分别是:
- 预处理(Prepressing)
- 编译(Compilation)
- 汇编(Assembly)
- 链接(Linking)
1.预编译:
首先是源代码文件main.c的预处理,这个预处理主要处理以"#"开始的预编译命令,主要包括:
- 对所有使用 #define 定义的语句进行展开
- 处理条件预编译指令,如 #if 、 #ifdef 、 #elif 、#else 、#endif 等
- 处理#include 预编译指令,将被包含的文件插入到预编译指令的位置(这可能是个递归过程,因为被包含的文件可能包含其他文件)
- 删除注释,如 // 、/**/
- 添加行号和文件名标识, 以便编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告能显示行号
- 保留所有的#pragma编译器指令,因为编译器须要使用它们
我们可以使用GCC参数-E表示只进行预编译,预编译生成的.i文件不包含任何宏定义,因为所有的宏已经被展开,所以我们可以使用-E选项调试宏函数。
gcc -E main.c -o main.i
2. 编译
编译过程就是把预处理完成的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。
这个过程往往是我们所说的整个程序构建的核心部分,也是最复杂的部分之一。
我们可以使用gcc 的 -S 参数进行编译。
gcc -S main.i -o main.s
当然,你也可以使用gcc -S main.c -o main.s直接生成汇编文件.s
3. 汇编
汇编器就是将汇编文件代码转变成机器可以执行的指令,每一个汇编语句几乎对应一条机器指令,所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令对照表一一翻译就可以了。上面的汇编过程我们可以用汇编器as来完成
as main.s -o main.o
或
gcc -c main.c -o main.o
4. 链接
链接过程可以简单理解为“组装”过程。链接的过程主要包括了地址和空间分配,符号决议,和重定位等步骤。
这些过程可以参考我后面的文章详解。
附上一图详细直观点: