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

x3naxklr  于 2022-11-12  发布在  其他
关注(0)|答案(9)|浏览(150)

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

rqmkfv5c

rqmkfv5c1#

(See这个答案的历史记录,以获得更详细的文本,但我现在认为读者更容易看到真实的的命令行)。
以下所有命令共享的公用文件

// 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

链接器从左到右搜索,并记录下未解析的符号。如果库解析了符号,它将使用该库的目标文件来解析符号(本例中是libb.a中的b.o)。
静态库彼此之间的依赖关系的作用相同-需要符号的库必须在前,然后是解析符号的库。
如果一个静态库依赖于另一个库,但另一个库又依赖于前一个库,则存在循环。可以通过用-(-)封闭循环依赖的库来解决此问题,例如-( -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

这里也是一样的--库必须跟随程序的目标文件。这里与静态库的区别在于,你不需要关心库之间的依赖关系,因为 * 动态库会自己整理它们的依赖关系 *。
最近的一些发行版显然默认使用--as-needed链接器标志,它强制程序的目标文件在动态库之前出现。我最近的archlinux发行版在默认情况下不使用这个标志,所以它不会因为没有遵循正确的顺序而给予错误。
在创建b.so时忽略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以后依赖于另一个库,那么这个二进制文件就需要重新链接。如果其他人在运行时使用dlopen加载libb(考虑动态加载插件),调用也会失败。所以"right"真的应该也是wrong

wlp8pajw

wlp8pajw2#

GNU ld链接器是一个所谓的智能链接器。它会跟踪前面的静态库所使用的函数,永久性地从它的查找表中丢弃那些没有使用的函数。其结果是,如果你过早地链接一个静态库,那么在链接行后面的静态库就不能再使用该库中的函数了。
典型的UNIX链接器是从左到右工作的,所以把所有依赖库放在左边,满足这些依赖关系的库放在链接行的右边。你可能会发现,一些库依赖于其他库,而同时其他库也依赖于它们。这就是问题变得复杂的地方。当涉及到循环引用时,修复你的代码!

brvekthn

brvekthn3#

这里有一个例子来说明当涉及到static库时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
ddrv8njm

ddrv8njm4#

如果您将-Wl,--start-group添加到链接器标志中,则它不关心它们的顺序或是否存在循环依赖。
在Qt上,这意味着添加:

QMAKE_LFLAGS += -Wl,--start-group

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

rkue9o1l

rkue9o1l5#

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

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

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

fgw7neuy

fgw7neuy6#

一个简单的提示绊倒了我:如果你用“gcc”或“g++"调用链接器,那么使用“--start-group”和“--end-group”不会将这些选项传递给链接器--也不会标记一个错误。如果你的库顺序错误,它只会使链接失败,并带有未定义的符号。
您需要将它们写为“-Wl,--start-group”等,以告知GCC将参数传递给链接器。

1zmg4dgp

1zmg4dgp7#

您可以使用-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.组内的顺序很重要!下面是一个示例:调试库具有调试例程,但非调试库具有相同的弱版本。您必须将调试库放在组中的第一位,否则将解析为非调试版本。
1.您需要在组列表中的每个库前面加上-Xlinker

wd2eg0qa

wd2eg0qa8#

链接顺序确实很重要,至少在某些平台上是这样。我看到过应用程序以错误的顺序链接库时崩溃的情况(错误意味着A链接在B之前,但B依赖于A)。

xv8emn3q

xv8emn3q9#

我已经看到了很多,我们的一些模块链接超过100个库,我们的代码加上系统和第三方库。
根据不同的连接器HP/Intel/GCC/SUN/SGI/IBM/等,您可以获得未解析的函数/变量等,在某些平台上,您必须列出两次库。
在大多数情况下,我们使用库、核心、平台、不同抽象层的结构化层次结构,但对于某些系统,您仍然必须按照链接命令中的顺序进行操作。
一旦你偶然发现了一个解决方案,就把它文档化,这样下一个开发人员就不必再去解决它了。
我以前的讲师常说,“* 高凝聚力和低耦合性 *",这句话今天仍然适用。

相关问题