文件的编译、连接
一个可执行程序是怎么由源文件生成的?
这个过程到底经历了什么?
废话不多讲,就从如下代码开始分析:
//test.c文件
#include<stdio.h>
extern int Add(int x, int y);
int main()
{
int a = 10;
int b = 6;
int sum = Add(a, b);
printf("a + b = %d\n", sum);
return 0;
}
//Add.c文件
int Add(int x, int y)
{
return x + y;
}
这段代码从源文件到可执行程序经历了什么?
大体上来说,如下图
可是此段过程编译器到底干了什么事呢?
没错,编译和连接,继续往下看
由于vs2022集成开发环境不易演示每个细节
我们用linux gcc来演示编译和链接
先来看看预编译会做什么事:
这个里的gcc test.c -E是为了编译test.c让其在预编译的时候停下来
回车一按,会发现屏幕上出现了很多东西,为了方便观察,将他移至test.i文件中,便于观察
你会发现,忽然就有八百多行的东西
这是什么呢?
对比一下之前的代码,不难发现就是将stdio.h里面的东西包含进来了
也就说包含预处理指令,那如果#define MAX 100也会在预编译时也会被替换进来吗
答案是肯定的!
假如定义int z = MAX;会发现,在预编译时MAX就被替换成100
不卖关子,实际上也包含了注释删除
总的来说,预编译实际上就干了这几件事
那么有同学会问了,预编译后面的编译阶段停下来,会发生呢?
往下走~
如果通过gcc test.i -S(停留在编译阶段),就会发现多了如下几个文件
同样的方式编译Add.i
这个过程实际上会经历这样一个过程,如下图:
那么如果你想在汇编阶段停下来,如下
回车后:
会发现多了.o文件
注意:这里说明一点,windows环境下编译出来的目标文件为xxx.obj,linux环境下编译出的目标文件为xxx.o
如果你好奇,打开了test.o文件,就会出现如下场景:
哎呀,怎么是这样一堆乱码啊?
实际上这里的目标文件就是二进制文件~
汇编仅仅是干这件事吗?
远远不知,实际上还会形成符号表
什么是符号表呢?
如下图:
总的来说,汇编实际上干了这么一件事:
汇编完后就会立马进入链接~
接下来会发生什么呢?
链接链接,自然是要链接一些东西呀,什么呢?
还记得之前形成的符号表吗?
咱就是要干这么几件事,如下:
怎么链接的呢?
实际上会干这么几点事:
1.合称段表
test.o和Add.o文件实际上形成各自的段表,这一过程便是将相同的段合并在一起
如下图:
接下来还有一步:符号表的合成和重定位
这两部之间到底是怎样的,究竟干了什么?
实际上如下:
实际上就联系起来了啊~
想象一下,如果没有Add.o这个函数主体,只有引入extern Add
假设屏蔽:
相当于Add只有那个无效地址,所以如果执行以下代码:
运行结果:
你会发现,他会在链接期间报错,恰好与刚刚的Add无效地址吻合,这样一个无效的地址在链接形成新的符号表后还是无效地址,也恰好表明Add这个函数不存在,所以程序无法执行通过;
恰好也证明一点:如果没有extern int Add(int , int); 强行执行代码,也可以运行过去,因为Add的有效地址存在;
也就是说,链接期间干了这么几件事:
从原理上讲,大致就是这样一个过程~