为什么库链接的顺序有时会导致GCC错误?

时间:2008-09-05 02:24:20

标签: c++ gcc linker

为什么链接库的顺序有时会导致GCC错误?

9 个答案:

答案 0 :(得分:524)

(请参阅此答案的历史记录以获取更详细的文本,但我现在认为读者更容易看到真正的命令行。)


以下所有命令共享的公共文件

$ 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

链接器从左向右搜索,并记录未解析的符号。如果库解析符号,它将获取该库的目标文件来解析符号(在这种情况下,bb超出了libb.a)。

静态库相互依赖的工作方式相同 - 需要符号的库必须是第一个,然后是解析符号的库。

如果静态库依赖于另一个库,但另一个库再次依赖于前一个库,则存在一个循环。您可以通过-(-)封闭循环相关库来解决此问题,例如-( -la -lb -)(您可能需要逃避问题,例如-\(和{{1 }})。然后,链接器会多次搜索这些封闭的lib,以确保解析循环依赖关系。或者,您可以多次指定库,因此每个库都在彼此之前:-\)

链接到动态库

-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发行版默认不使用此标志,因此不会因为没有遵循正确的顺序而给出错误。

在创建前者时,忽略--as-neededb.so的依赖关系是不正确的。在链接d.so时,您需要指定库,但a本身并不需要整数a,因此不应该关注b }自己的依赖。

如果您错过了指定b

的依赖关系,以下是一个含义示例
libb.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加载libb(考虑动态加载插件),则调用也会失败。所以dlopen也应该是"right"

答案 1 :(得分:95)

GNU ld链接器是一个所谓的智能链接器。它将跟踪前面的静态库使用的函数,永久地抛弃那些未在其查找表中使用的函数。结果是,如果过早链接静态库,那么稍后链接行上的静态库将不再提供该库中的函数。

典型的UNIX链接器从左到右工作,因此将所有依赖库放在左侧,将满足这些依赖性的库放在链接行的右侧。您可能会发现某些库依赖于其他库,而同时其他库依赖于它们。这是它变得复杂的地方。当涉及循环引用时,修复你的代码!

答案 2 :(得分:51)

这是一个示例,用于说明在涉及静态库时,GCC的工作原理。所以我们假设我们有以下情况:

  • myprog.o - 包含main()功能,取决于libmysqlclient
  • libmysqlclient - 静态,为了示例(当然,您更喜欢共享库,因为libmysqlclient很大);在/usr/local/lib;并依赖libz
  • 的内容
  • libz(动态)

我们如何链接? (注意:使用gcc 4.3.4编译Cygwin的例子)

gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init'
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# we have to link with libz, too

gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works

答案 3 :(得分:25)

如果将-Wl,--start-group添加到链接器标志,则不关心它们处于哪个顺序或者是否存在循环依赖关系。

在Qt上,这意味着添加:

QMAKE_LFLAGS += -Wl,--start-group

节省了大量的时间,它似乎没有减慢链接的速度(这比编译时间要少得多)。

答案 4 :(得分:8)

另一种方法是两次指定库列表:

gcc prog.o libA.a libB.a libA.a libB.a -o prog.x

这样做,您不必为正确的序列烦恼,因为参考将在第二个块中解析。

答案 5 :(得分:5)

您可以使用-Xlinker选项。

g++ -o foobar  -Xlinker -start-group  -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a  -Xlinker -end-group 

几乎等于

g++ -o foobar  -Xlinker -start-group  -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a  -Xlinker -end-group 

小心!

  1. 组内的顺序很重要! 这是一个例子:调试库有一个调试例程,但是非调试 库有一个弱版本。您必须放置调试库 首先在组中或您将解析为非调试版本。
  2. 您需要在组列表中的每个库之前使用-Xlinker

答案 6 :(得分:4)

快速提示让我失望:如果您将链接器调用为“gcc”或“g ++”,则使用“--start-group”和“--end-group”将不会传递这些选项通过链接器 - 它也不会标记错误。如果您的库顺序错误,它将使用未定义符号的链接失败。

你需要将它们写成“-Wl, - start-group”等,告诉GCC将参数传递给链接器。

答案 7 :(得分:3)

我已经看过很多,我们的一些模块链接超过100个代码库以及系统&第三方库。

根据不同的链接器HP / Intel / GCC / SUN / SGI / IBM / etc,您可以获得未解析的函数/变量等,在某些平台上,您必须两次列出库。

在大多数情况下,我们使用库,核心,平台,不同抽象层的结构化层次结构,但对于某些系统,您仍然必须使用链接命令中的顺序。

一旦你找到解决方案文档,下一个开发人员就不必再次解决它了。

我的老讲师曾经说过,“高凝聚力和低耦合”,今天仍然如此。

答案 8 :(得分:2)

链接顺序当然重要,至少在某些平台上。我看到与错误顺序的库链接的应用程序崩溃(错误意味着A在B之前链接但B依赖于A)。