为何C++静态链接库顺序很重要
自从换了新环境,逐渐接触了一些机器学习相关库的过程中,不可避免的开始捡回 C/C++ 的一些知识,之后也会写一些 C/C++ 相关的文章。
一个编译错误
今天,我们从一个编译错误说起,之后再复习相关的知识:
$ g++ bar.cpp
bar.cpp:(.text+0x20): undefined reference to `foo`
从字面上理解的话,就是 foo
引用没有被定义,一般来说,这种错误多半是由于链接器链接的时候没有找到 foo
的定义,也就是说,你提供的库不对,或者,不够。
如果有点懵,我们简单复习下 C/C++ 项目的编译。
编译步骤
我们在编译一个 C/C++ 项目时,大致有这么三个步骤[1]:
- 预处理:将
#include
以及#define
相关的代码,预处理成纯 C/C++ 代码; - 编译:编译成目标文件,即 obj 文件,一般都是
.o
文件; - 链接:将 obj 文件,与相关的库文件链接在一起,最终生成一个可执行文件或者库文件;
所以,这个错误是在第三个步骤出错的,检查下你的编译选项,然后添加正确的库基本上就能解决了。
$ g++ -L -lfoo bar.cpp
显然,如果解决问题那么简单,我是不会拿它写博客的 :P。
问题还没解决,上面的解决方式显然能够解决大部分情况,然而有些情况下,我们的编译选项中,明明定义了所有的库,为什么还是编译不通过?
就不卖关子了,答案是静态链接时库的顺序也会造成这个问题。
至于原因,就需要了解链接过程的一些细节了。
链接过程
我们在编译的过程中,有两种链接方式,动态与静态。
- 静态链接:即将依赖库与调用程序链接成一个完成的库或者可执行文件,运行的时候会将整个程序装到内存中,方便部署但是体积较大,依赖库升级的时候需要重新编译;
- 动态链接:即将依赖库与调用程序分离,不组装成单个文件,而是在运行的时候,当调用到动态库的库时,才会将依赖库装载到内存中,这样会方便与其它程序共享以及升级,可执行文件的体积小,但是不方便部署;
今天我们遇到的问题,发生在静态链接过程中,而链接过程的细节如下:
静态库中,包含着所有的 obj(*.o) 文件,连接器从左至右搜索,维护着一个 undefined 列表,一旦遇到没有定义的内容,就会将它加到列表中,如果搜索到了定义的内容,则抽取出 obj 文件,进行链接,并将 undefined 内容移出列表,而其它 obj 文件就会被丢弃(为了减少最终的体积大小),于是一个静态库如果不能在搜索过程中被链接,它就会被丢弃,而在后面一旦遇到依赖它的库,就会造成引用无法被链接,一直留在undefined 列表中,最终导致编译错误。
拿一个简单的例子来说明[2]:
1 | $ cat a.cpp |
1 | $ g++ -c b.cpp -o b.o |
我们可以看到,d 是 b 的依赖,如果 d 先于 b 出现,由于无法链接 d 就会被抛弃,而造成 b 中的 extern int b
无法被链接,造成错误。
至于动态链接,链接器会对依赖进行整理,避免这个问题。
知识拓展之环形引用
如果是环形引用(这里只是为了说明问题,实际编程中,这种问题应该尽量避免),情况又改如何?[3]
我们修改下上面的例子:
1 | $ cat a.cpp |
运行编译后,你会发现
1 | $ g++ a.cpp -L. -lb -ld |
这种方式是能顺利编译的,为什么?
留给你思考,然后我们再来个复杂点的:
1 | $ cat a.cpp |
再运行上面的编译后:
1 | $ g++ -c e.cpp -o e.o |
会发现那两种方式都不能解决问题。
而下面这种方式却能解决
1 | $ g++ a.cpp -L. -lb -ld -lb |
为什么?
很简单,因为回顾下链接的过程就能发现,当链接器遇到第一个 lb
时,会将 b 加入 undefined 列表,而遇到 ld
时,会将 b 与 ld
链接,同时将 d 与 e 加入 undefined 列表,最后遇到 第二个 lb
时,重复同样的过程,然后顺利链接。
但是,反过来:
1 | $ g++ a.cpp -L. -ld -lb -ld |
却会失败,为什么?也留给你了 :P。
知识拓展之性能影响
链接顺序也会影响性能?
是的,比如 math 相关的性能加强库 libmopt 便是一个例子[4],顺序不对会造成连接器使用系统默认的库,而不是你指定的库。
Ref
- https://stackoverflow.com/questions/6264249/how-does-the-compilation-linking-process-work
- https://stackoverflow.com/questions/45135/why-does-the-order-in-which-libraries-are-linked-sometimes-cause-errors-in-gcc
- https://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking
- https://blogs.oracle.com/d/library-order-is-important
首发于 Github issues: https://github.com/xizhibei/blog/issues/100 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可作者:习之北 (@xizhibei)
原链接:https://blog.xizhibei.me/zh-cn/2019/02/24/why-library-order-matters-in-cpp-static-linking/