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


Answers:


557

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


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

$ 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

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

静态库彼此之间的依赖关系是相同的-必须首先使用需要符号的库,然后是解析符号的库。

如果静态库依赖于另一个库,但是另一个库又依赖于前一个库,则存在一个循环。您可以通过用-(和包围循环依赖的库来解决此问题-),例如-( -la -lb -)(您可能需要转义括号,例如-\(-\))。链接器然后多次搜索那些包含的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

这里是相同的-库必须遵循程序的目标文件。与静态库相比,这里的区别在于您不必在意彼此之间的依赖关系,因为动态库本身会解决它们的依赖关系

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

这是不正确的省略的依赖b.sod.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


10
重复进行,直到所有符号都解决了,嗯-您认为它们可以管理拓扑排序。LLVM本身具有78个静态库,并且具有“谁知道”什么依赖性。的确,它还有一个脚本可以弄清楚编译/链接选项-但是您不能在所有情况下都使用它。
2010年

6
@Steve就是程序lorder+ tsort所做的。但是,如果您有循环引用,则有时没有顺序。然后,您只需要循环浏览库列表,直到解决所有问题。
Johannes Schaub-litb

10
@Johannes-确定最大的强连通组件(例如Tarjans算法),然后对组件的(固有非循环)有向图进行拓扑排序。每个组件都可以视为一个库-如果需要该组件中的任何一个库,则依赖周期将导致需要该组件中的所有库。因此,没有,实际上没有必要遍历所有库来解决所有问题,也不需要笨拙的命令行选项-一种使用两种著名算法的方法可以正确处理所有情况。
2011年

4
我想为这个出色的答案添加一个重要的细节:使用“-(archives-)”或“ --start-group archives --end-group” 是解决循环依赖关系的唯一可靠方法,因为每次链接器访问归档文件时,它仅拉入(并注册的未解析符号的)目标文件,这些目标文件可解析当前未解析的符号。因此,CMake重复依赖图中的已连接组件的算法有时可能会失败。(有关详细信息,另请参见Ian Lance Taylor关于链接程序的出色博客文章。)
jorgen

3
您的回答帮助我解决了我的链接错误,并且您已经非常清楚地说明了如何避免遇到麻烦,但是您有任何想法为什么要这样设计?
Anton Daneyko 2014年

102

GNU ld链接器是所谓的智能链接器。它将跟踪先前的静态库使用的功能,并永久性地将其查找表中未使用的那些功能扔掉。结果是,如果您过早地链接静态库,则该静态库中的函数以后在链接行上将不再对静态库可用。

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


2
这仅是gnu ld / gcc吗?还是链接器常见的东西?
Mike

2
显然,更多的Unix编译器也有类似的问题。MSVC并非完全摆脱这些问题,但是看起来似乎并不那么糟糕。
MSalters 2009年

4
MS开发人员工具不太会显示这些问题,因为如果您使用全MS工具链,它最终会正确设置链接器顺序,并且您永远不会注意到该问题。
迈克尔·科恩

16
MSVC链接器对此问题不太敏感,因为它将在所有库中搜索未引用的符号。如果多个库都有该符号,则库顺序仍然会影响解析哪个符号。从MSDN:“也按命令行顺序搜索库,但要注意以下几点:从库中导入目标文件时未解析的符号将首先在该库中搜索,然后在命令行中搜索以下库, / DEFAULTLIB(指定默认库)指令,然后转到命令行开头的任何库”
Michael Burr

4
“ ...智能链接器...” -我认为它被归类为“单通”链接器,而不是“智能链接器”。
jww

54

这是一个示例,用于明确说明涉及静态库时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

31

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

在Qt上,这意味着添加:

QMAKE_LFLAGS += -Wl,--start-group

节省了很多时间,而且似乎并没有减慢链接的速度(总之比编译要少得多的时间)。


8

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

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

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


5

您可以使用-Xlinker选项。

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

ALMOST等于

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

小心点!

  1. 组内的顺序很重要!这是一个示例:调试库具有调试例程,但非调试库具有相同的弱版本。您必须将调试库FIRST放在该组中,否则您将解析为非调试版本。
  2. 您需要在组列表中的每个库之前加上-Xlinker

5

一个让我震惊的提示:如果您以“ gcc”或“ g ++”的形式调用链接器,则使用“ --start-group”和“ --end-group”不会将这些选项传递给链接器-也不会标记错误。如果您有错误的库顺序,它将导致带有未定义符号的链接失败。

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


2

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


2

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

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.