写在前面
本文大部分内容翻译自Library order in static linking
静态链接顺序很重要
在gcc编译时,静态链接库链接顺序会影响链接结果和行为。
举例如下:
$ cat simplefunc.c
int func(int i) {
return i + 21;
}
$ cat simplemain.c
int func(int);
int main(int argc, const char* argv[]) {
return func(argc);
}
$ gcc -c simplefunc.c
$ gcc -c simplemain.c
$ gcc simplefunc.o simplemain.o
$ ./a.out ; echo $?
22
$ gcc simplemain.o simplefunc.o
$ ./a.out ; echo $?
22123456789101112131415161718192021
对于object文件,链接顺序是没有影响的,gcc会将所有的目标文件加入到链接过程中。
$ ar r libsimplefunc.a simplefunc.o
$ ranlib libsimplefunc.a
$ gcc simplemain.o -L. -lsimplefunc
$ ./a.out ; echo $?
22
$ gcc -L. -lsimplefunc simplemain.o
simplemain.o: In function 'main':
simplemain.c:(.text+0x15): undefined reference to 'func'
collect2: ld returned 1 exit status12345678910
当链接器遇到libsimplefunc.a时,它仍然没有看到simplemain.o,这意味着func尚未出现在未定义的列表中。当链接器查看库时,它会看到导出func的simplefunc.o。但由于它不需要func,因此该目标文件不包含在链接中。当链接器确实到达simplemain.o并且看到func确实是必需的时,它被添加到未定义的列表中(因为它不在导出的列表中)。链接器然后到达链接的末尾并且func仍未定义。
请注意在先前的链接顺序中不会发生这种情况 - 因为simplemain.o首先出现,func在链接器看到库之前位于未定义的列表中,因此导出它的目标文件确实包含在内。
从上面的例子中可以得出:静态链接时,链接库顺序会影响链接结果。如果对象或库AA需要来自库BB的符号,则AA应该在链接器的命令行调用中位于库BB 之前。
下面一节,我们将从原理上来了解链接库的顺序是如何影响链接结果的!
静态链接过程
程序静态链接的过程如下,举例说明(翻译Library order in static linking的The linking process一节)
$ gcc main.o -L/some/lib/dir -lfoo -lbar -lbaz1
注:库指library,对象指object,此处要注意区分。
1 当链接时,链接器维护两个列表:
1.1 到目前为止遇到的所有目标文件(object)和库(library)导出的符号列表,记做exports(导出的符号)
1.2 遇到的目标文件(object)和库(library)请求导入但尚未找到的未定义符号列表,即undefined reference,记做imports(待导入的符号)
2 当链接器遇到新的目标文件(object)时,
2.1 它导出的符号会被添加到上面提到的导出符号列表中。 如果任何符号位于未定义列表中,则会从那里删除它,因为现在已找到它。 如果导出列表中已有任何符号,则会出现“多重定义”错误:两个不同的对象导出相同的符号,链接器会报错
2.2 它导入的符号被添加到未定义符号列表中,除非它们可以在导出符号列表exports中找到。
3 当链接器遇到新库时,事情会更有趣。 链接器遍历库中的所有目标文件(object),库是object打包成的一个整体。 对于每一个,它首先查看它导出的符号
3.1 如果它导出的任何符号位于未定义列表中,则该对象将添加到链接中,并执行下一步。 否则,将跳过下一步。
3.2 如果目标文件(object)已添加到链接中,则按上述方式对其进行处理 - 未定义和导出的符号将添加到符号表中。
3.3 如果库中的任何目标文件(object)已包含在链接中,则会再次重新扫描整个库,因为这个目标文件可能依赖库中的其他目标文件。
链接器完成后,它会查看符号表。 如果任何符号保留在未定义的列表中,链接器将抛出“未定义的引用”错误。
在链接器查看库之后,它将不会再次查看它,即链接器只查看库一次。 即使它导出一些后来的库可能需要的符号。 链接器返回重新扫描对象的唯一时间它已经在单个库中发生 - 如上所述,一旦某个库中的对象被带入链接,同一库中的所有其他对象将被重新扫描。 传递给链接器的标志可以调整这个过程 - 再次,我们稍后会看到一些例子。
另外,检查库时,如果不提供符号表所需的符号,则可以将其中的目标文件排除在链接之外。 这是静态链接的一个非常重要的特性。 我之前提到过的C库大量使用了这个功能,主要是将自身分解为每个函数的一个对象。 因此,例如,如果您的代码使用的唯一C标准库函数是strlen,则只有strlen.o将从libc.a进入链接 - 并且您的可执行文件将非常小。
建议
在项目开发过层中尽量让lib是垂直关系,避免循环依赖,越是底层的库,越是往后面写
若存在循环依赖的情况,可用以下方法解决
Xlinker "-("-la -lb -lc"-)" OR--start-group -la -lb lc -Wl,--end-group
参考文档
https://blog.csdn.net/caikunbob/article/details/85550000
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。