今天遇到了一个关于链接器的错误,百思不得其解,查阅资料后才得知链接器的链接过程。
以下内容部分摘自《深入理解计算机系统》
unix链接器在解析外部引用时,在符号解析的阶段,链接器从左到右按照它们在编译器驱动程序命令行上出现的相同顺序来扫描可重定位目标文件和库文件。在这次扫描中,链接器维持一个可重定位目标文件的集合E(这个集合中的文件会被合并起来形成可执行文件),一个未被解析的符号(即被引用了但是尚未定义的符号)集合U,以及一个在前面输入文件中已定义的符号集合D。初始时,E、U和D都是空的。
对于命令行上的每个输入文件f,链接器会判断f是一个目标文件还是一个库文件。如果f是一个目标文件,那么链接器把f添加到E,修改U和D来反映f中的符号定义和引用,并继续下一个输入文件。
如果f是一个库文件,那么链接器就尝试匹配U中未解析的符号和由库文件成员定义的符号。如果某个库文件成员m定义了一个符号来解析U中的一个引用,那么就将m加到E中,并且链接器修改U和D来反映m中的符号定义和引用。对库文件中所有的成员目标文件都反复进行这个过程,直到U和D都不再发生变化。在此时,任何不包含在E中的成员目标文件都简单地被抛弃,而链接器将继续处理下一个输入文件。
如果当链接器完成对命令行上输入文件的扫描后,U是非空的,那么链接器就会输出一个错误并终止。否则,它会合并和重定位E中的目标文件,从而构建输出的可执行文件。
这样的算法会导致一些由于命令行上库文件和目标文件的顺序不恰当而造成的错误。在命令行中,如果定义一个符号的库出现在引用这个符号的目标文件之前,那么引用就不能被解析,链接会失败,如下的案例。
首先构建一个库函数add.c
int add(int x,int y) { return x+y; }
加入库libadd.a
gcc -c add.c -o add.o
ar rcs libadd.a add.o
构建主函数test.c
#include<stdio.h> int main() { int a,b; int add(int,int); while(scanf("%d %d",&a,&b)) { printf("%d\n",add(a,b)); } return 0; }
链接方式1:
gcc test.c libadd.a -o test.exe 链接成功
链接方式2:
gcc ./libadd.a test.c -o test.exe 链接失败
关于库的一般准则是将它们放在命令行的结尾。如果各个库的成员是相互独立的(也就是说没有成员引用另一个成员定义的符号),那么这些库就可以按照任何顺序放置在命令行的结尾处。
另一方面,如果库不是相互独立的,那么它们必须排序,使得每个库成员文件外部引用的符号s,在命令行中至少有一个s的定义是在对s的引用之后的。
如果有必要,为了满足依赖需求,可以在命令行上重复库。