为什么在GCC中,库的链接顺序有时会导致错误?
当前回答
(请参阅这个答案的历史以获得更详细的文本,但我现在认为读者更容易看到真正的命令行)。
以下所有命令共享的公共文件
// a depends on b, b depends on d
$ cat a.cpp
extern int a;
int main() {
return a;
}
$ cat b.cpp
extern int b;
int a = b;
$ cat d.cpp
int b;
链接到静态库
$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o
$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order
链接器从左到右搜索,并记录未解决的符号。如果一个库解析了该符号,它将该库的对象文件用于解析该符号(b.o out of libb。在这种情况下是A)。
静态库之间相互依赖的工作原理是一样的——需要符号的库必须在前面,然后是解析符号的库。
如果一个静态库依赖于另一个库,但另一个库又依赖于前一个库,就会出现一个循环。你可以通过-(和-)封闭循环依赖的库来解决这个问题,比如-(-la -lb -)(你可能需要转义括号,比如-\(和-\))。然后链接器会多次搜索这些包含的库,以确保循环依赖关系被解析。或者,您可以多次指定库,因此每个库都在另一个库之前:-la -lb -la。
链接到动态库
$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!
$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order
这里也是一样——库必须遵循程序的目标文件。与静态库相比,这里的不同之处在于,您不需要关心库之间的依赖关系,因为动态库自己会对它们的依赖关系进行排序。
最近的一些发行版显然默认使用——按需链接器标志,该标志强制程序的目标文件出现在动态库之前。如果传递了该标志,链接器将不会链接到可执行文件实际不需要的库(它从左到右检测这一点)。我最近的archlinux发行版在默认情况下没有使用这个标志,所以它不会因为没有遵循正确的顺序而给出错误。
在创建b.so对d.so的依赖关系时,省略b.so对d.so的依赖关系是不正确的。当链接a时,您将被要求指定库,但a并不真正需要整数b本身,因此不应该让它关心b自己的依赖关系。
下面是一个示例,说明如果您没有指定lib .so的依赖项将会产生什么影响
$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)
$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"
如果您现在研究一下二进制文件的依赖关系,您会注意到二进制文件本身也依赖于libd,而不仅仅是它应该依赖的libb。如果libb以后依赖于另一个库,则需要重新链接二进制文件。如果其他人在运行时使用dlopen加载libb(考虑动态加载插件),调用也会失败。所以“正确”也应该是错误的。
其他回答
我已经见过很多次了,我们的一些模块链接了超过100个我们的代码库加上系统和第三方库。
根据HP/Intel/GCC/SUN/SGI/IBM/等不同的连接器,你可以得到未解析的函数/变量等,在某些平台上,你必须列出库两次。
在大多数情况下,我们使用库、核心、平台、不同抽象层的结构化层次结构,但对于某些系统,您仍然必须使用link命令中的顺序。
一旦你找到了一个解决方案,那么下一个开发人员就不必再做了。
我以前的老师常说,“高内聚低耦合”,今天依然如此。
另一种方法是两次指定库列表:
gcc prog.o libA.a libB.a libA.a libB.a -o prog.x
这样做,您不必为正确的序列而烦恼,因为引用将在第二个块中解析。
GNU ld链接器是所谓的智能链接器。它将跟踪之前静态库使用的函数,永久地从它的查找表中丢弃那些没有使用的函数。结果是,如果您过早地链接一个静态库,那么该库中的函数将不再对链接线上的静态库可用。
典型的UNIX链接器从左到右工作,因此将所有依赖的库放在左边,而满足这些依赖关系的库放在链接行的右边。您可能会发现一些库依赖于其他库,而同时其他库也依赖于它们。这就是事情变得复杂的地方。当涉及到循环引用时,修复您的代码!
(请参阅这个答案的历史以获得更详细的文本,但我现在认为读者更容易看到真正的命令行)。
以下所有命令共享的公共文件
// a depends on b, b depends on d
$ cat a.cpp
extern int a;
int main() {
return a;
}
$ cat b.cpp
extern int b;
int a = b;
$ cat d.cpp
int b;
链接到静态库
$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o
$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order
链接器从左到右搜索,并记录未解决的符号。如果一个库解析了该符号,它将该库的对象文件用于解析该符号(b.o out of libb。在这种情况下是A)。
静态库之间相互依赖的工作原理是一样的——需要符号的库必须在前面,然后是解析符号的库。
如果一个静态库依赖于另一个库,但另一个库又依赖于前一个库,就会出现一个循环。你可以通过-(和-)封闭循环依赖的库来解决这个问题,比如-(-la -lb -)(你可能需要转义括号,比如-\(和-\))。然后链接器会多次搜索这些包含的库,以确保循环依赖关系被解析。或者,您可以多次指定库,因此每个库都在另一个库之前:-la -lb -la。
链接到动态库
$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!
$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order
这里也是一样——库必须遵循程序的目标文件。与静态库相比,这里的不同之处在于,您不需要关心库之间的依赖关系,因为动态库自己会对它们的依赖关系进行排序。
最近的一些发行版显然默认使用——按需链接器标志,该标志强制程序的目标文件出现在动态库之前。如果传递了该标志,链接器将不会链接到可执行文件实际不需要的库(它从左到右检测这一点)。我最近的archlinux发行版在默认情况下没有使用这个标志,所以它不会因为没有遵循正确的顺序而给出错误。
在创建b.so对d.so的依赖关系时,省略b.so对d.so的依赖关系是不正确的。当链接a时,您将被要求指定库,但a并不真正需要整数b本身,因此不应该让它关心b自己的依赖关系。
下面是一个示例,说明如果您没有指定lib .so的依赖项将会产生什么影响
$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)
$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"
如果您现在研究一下二进制文件的依赖关系,您会注意到二进制文件本身也依赖于libd,而不仅仅是它应该依赖的libb。如果libb以后依赖于另一个库,则需要重新链接二进制文件。如果其他人在运行时使用dlopen加载libb(考虑动态加载插件),调用也会失败。所以“正确”也应该是错误的。
链接顺序确实很重要,至少在某些平台上是如此。我曾见过应用程序以错误的顺序与库链接而导致崩溃(错误意味着A在B之前链接,但B取决于A)。
推荐文章
- 当启用c++ 11时,std::vector性能回归
- 我如何打开(字面上)GCC的所有警告?
- Typedef定长数组
- 如何在C中抑制“未使用的参数”警告?
- libpthread.so。0:添加符号错误:命令行中缺少DSO
- 为什么GCC在实现整数除法时使用奇数乘法?
- g++对typeinfo的未定义引用
- 不能在Lion下安装Ruby与RVM - GCC问题
- 对负数的模运算
- 解决LNK4098: defaultlib 'MSVCRT'冲突
- “静态链接”和“动态链接”是什么意思?
- 什么是makeinfo,如何获取它?
- Clang vs GCC——哪个生成更快的二进制文件?
- 构造函数初始化列表求值顺序
- GCC转储预处理器定义