我们知道从代码到可执行程序要经过: 预处理/预编译 - > 编译 -> 汇编 -> 链接 -> 可执行程序,上一篇文章已经详细讲解了预处理 、编译、汇编是怎么进行的,都做了哪些动作,今天我们主要来看看链接器是如何把目标文件(.o/.obj文件)链接到一块的。
第一步:汇编的时候后会生成目标文件,这个目标文件是什么?
我写了两个源文件:
test2.c :
#include <stdio.h> //引用头文件
#define M 1000 //定义字符M 的值为1000
#define MUL(x) (x)*(x) //定义宏
extern int Add(int x,int y);
int main()
{
//打印
printf("预处理过程\n");
printf("i am a Mul:%d\n",MUL(3));
printf("i am a M:%d\n",M);
printf("x+y= %d\n",Add(2,3));
return 0;
}
add.c :
1 #include <stdio.h>
2 int Add(int x,int y)
3 {
4 return x+y;
5 }
把这两个源文件(.c) 分别生成目标对应的目标文件(.o) :
用readelf查看生成的符号表 :
我们看到在test2.o中的符号有 main 、printf 、Add,在add.o中的符号只有Add,
其实每个符号都有对应的地址,链接的时候就是通过符号和地址找到对应的函数
第二步:链接
链接的时候会做两件事:1.合并段表 2. 符号表的合并和重定位
合并段表很好理解,就是每个.o文件其实是一个elf格式的文件,每个elf文件都有很多段,在链接的时候把各自相同的段合并
我们再来看符号表的合并和重定位:
这个过程它会保留有用的符号和地址,如果地址找不到会报错,例如add.c生成的符号是 Add并且有一个有效地址0x1234(假设),test2.c里声明了函数Add,这里的Add也会生成符号Add但是它的地址是无效的,在链接的过程,相同的符号中会保留一个拥有有效地址的符号,并且和其它的符号main、printf等合并成新的符号表然后跟链接库链接在一起,这样就可以通过符号表把所有目标文件链接在一块了
第三步:生成可执行程序
把目标文件add.o test2.o 生成可执行程序 mybin , ./mybin执行程序,这样一个程序就成功被执行了。
注: 链接库分为静态链接库和动态链接库
静态链接库:静态链接库实现链接操作的方式很简单,即程序文件中哪里用到了库文件中的功能模块,GCC
编译器就会将该模板代码直接复制到程序文件的适当位置,最终生成可执行文件。
动态链接库:动态链接库,又称为共享链接库。和静态链接库不同,采用动态链接库实现链接操作时,程序文件中哪里需要库文件的功能模块,GCC
编译器不会直接将该功能模块的代码拷贝到文件中,而是将功能模块的位置信息记录到文件中,直接生成可执行文件。
GCC
编译器生成可执行文件时,默认情况下会优先使用动态链接库实现链接操作,除非当前系统环境中没有程序文件所需要的动态链接库,GCC
编译器才会选择相应的静态链接库。如果两种都没有(或者 GCC
编译器未找到),则链接失败。
总结:
1. 链接的时候做了两件事:合并段表、符号表的合并和重定位;
2.目标文件其实个elf格式的文件,每个elf文件都有自己的段位,链接的时候会把相同段合并
3.符号表的作用是在链接的时候通过符号和地址找到对应的函数,符号表合并的时候只会保留有有效地址的符号,如果找不到有效地址就会出错。通过合并后的符号表可以把所有目标文件链接在一块
4.链接库分动态链接和静态链接