528

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

4

9 回答 9

632

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


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

$ 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

链接器从左到右搜索,并记录未解析的符号。如果一个库解析了符号,它会使用该库的目标文件来解析符号(在这种情况下,bo 来自 libb.a)。

静态库相互之间的依赖关系是相同的——首先需要符号的库,然后是解析符号的库。

如果一个静态库依赖于另一个库,而另一个库又依赖于前一个库,则存在一个循环。您可以通过将循环依赖的库用-(and括起来来解决此问题-),例如-( -la -lb -)(您可能需要转义括号,例如-\(and -\))。然后,链接器会多次搜索这些包含的库,以确保解决循环依赖关系。或者,您可以多次指定库,因此每个库都在另一个之前:-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

这里也一样——库必须遵循程序的目标文件。与静态库相比,这里的不同之处在于您无需关心库之间的依赖关系,因为动态库自己会整理它们的依赖关系

一些最近的发行版显然默认使用--as-needed链接器标志,它强制程序的目标文件位于动态库之前。如果传递了该标志,则链接器将不会链接到可执行文件实际不需要的库(并且它会从左到右检测到这一点)。我最近的archlinux发行版默认不使用这个标志,所以它没有因为没有遵循正确的顺序而出错。

创建前者时省略b.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"真的应该wrong也是。

于 2009-01-03T17:53:31.210 回答
124

GNU ld 链接器是所谓的智能链接器。它将跟踪前面的静态库使用的函数,永久地从其查找表中丢弃那些未使用的函数。结果是,如果您过早地链接静态库,则该库中的函数在链接行后面的静态库中不再可用。

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

于 2009-01-03T17:21:37.827 回答
61

这是一个示例,可以清楚地说明在涉及静态库时如何使用 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
于 2011-07-16T12:32:27.010 回答
46

如果您添加-Wl,--start-group到链接器标志,它并不关心它们的顺序或是否存在循环依赖关系。

在 Qt 上,这意味着添加:

QMAKE_LFLAGS += -Wl,--start-group

节省了大量时间,而且似乎并没有减慢链接速度(无论如何,这比编译花费的时间要少得多)。

于 2015-04-05T12:24:00.340 回答
10

另一种选择是两次指定库列表:

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

这样做,您不必为正确的顺序而烦恼,因为引用将在第二个块中解决。

于 2014-03-21T10:07:22.260 回答
7

一个让我大吃一惊的快速提示:如果您将链接器调用为“gcc”或“g++”,那么使用“--start-group”和“--end-group”不会将这些选项传递给链接器——它也不会标记错误。如果您的库顺序错误,它只会使带有未定义符号的链接失败。

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

于 2013-08-11T14:07:54.740 回答
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. 组内顺序很重要!这是一个示例:调试库具有调试例程,但非调试库具有相同的弱版本。您必须将调试库 FIRST 放在组中,否则您将解析为非调试版本。
  2. 您需要在组列表中的每个库前面加上 -Xlinker
于 2011-08-09T08:06:22.357 回答
3

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

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

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

一旦你找到了一个解决方案,就将它记录下来,这样下一个开发人员就不必再次解决它。

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

于 2008-09-05T03:56:32.450 回答
2

链接顺序确实很重要,至少在某些平台上是这样。我已经看到以错误顺序链接到库的应用程序崩溃(其中错误意味着 A 在 B 之前链接,但 B 取决于 A)。

于 2009-04-28T13:57:01.670 回答