首先我们先看一遍.c文件到可执行程序文件的所经过的过程.
.c->预处理->编译->汇编->链接->a.out
我们写一个简单的样例代码,以这个为例子,我们来看看它都经历了什么.
test.c
#include <stdio.h>
#define A 2+3
int main()
{
printf("%d\n",A+A); //宏替换
return 0;
}
预处理命令: gcc -E test.c -o test.i
预处理:
1. 删除 #define 并展开所定义的宏
2. 处理所有条件编译指令,如:"#if","#ifdef","#endif"等
3. 插入头文件到"#include"处,可以递归方式进行处理(嵌入)
4. 删除所有的注释 “//” 和 “/**/”
5. 添加行号 和 文件名标识,以便编译时的编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时能够显示行号
6. 保留所有#pragma编译指令(编译器需要用)
注: 经过预处理后,得到的是预处理文件(如 hello.i) 它还是一个可读的文本文件,但不包含任何宏定义
编译:
命令: gcc -S test.i -o test.s
经过汇编生成的是汇编代码. 可惜CPU还是看不懂汇编代码,CPU只认识机器语言.而下一步操作就是将汇编代码变成机器代码.
汇编
命令: gcc -c test.s -o test.o
汇编结果是一个 可重定位目标文件(如 test.o) 其中包含的是不可读的二进制代码.
.o 文件是一个可重定位的 2进制文件,因为在链接之后,才能确定符号,变量,函数的地址,所以需要重新定位.
静态链接:
命令: gcc -static -o test test.o
链接的主要内容: 链接的主要内容就是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接.
链接操作的步骤:
1.确定符号引用关系(符号解析)
2.合并相关 .o 文件
3.确定每个符号的地址
4.在指令中填入新地址
链接的好处
1.模块化
一个程序可以分成很多源程序文件
可构建共享函数库,如数学,c标准库
2.效率高
时间上,可分开编译
空间上,无需包含共享库函数的源码,只要直接调用即可
参考书籍: 程序员的自我修养