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

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

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


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

 $ 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 bo $ ar cr libb.a bo $ g++ -c d.cpp -o do $ ar cr libd.a do $ 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 

链接器从左到右search,并logging未parsing的符号。 如果一个库parsing了这个符号,它就需要这个库的目标文件来parsing这个符号(在这个例子中是从libb.a开始的)。

静态库相互依赖的工作是相同的 – 需要符号的库必须先是库,然后是parsing符号的库。

如果静态库依赖于另一个库,而另一个库又依赖于以前的库,则存在一个循环。 你可以通过用-(-) (例如-( -la -lb -)来包含循环相关的库来解决这个问题(你可能需要转义parens,比如-\(-\) )。 链接器然后多次search那些封装的lib以确保循环依赖关系得到解决。 或者,您可以多次指定这些库,因此每个库都在另一个之前: -la -lb -la

链接到dynamic库

 $ 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 

这里也一样 – 库必须遵循程序的目标文件。 与静态库的不同之处在于,您不必关心库彼此之间的依赖关系,因为dynamic库本身就会自行排列它们的依赖关系

一些最近的发行版显然默认使用--as-needed链接器标志,这强制了程序的目标文件在dynamic库之前。 如果该标志被传递,链接器将不会链接到可执行文件实际不需要的库(并且从左到右检测到)。 我最近的archlinux发行版默认不使用这个标志,所以没有按照正确的顺序给出错误。

在创build前者时,省略b.sod.so的依赖关系是不正确的。 当连接a时,你将需要指定这个库,但是a并不需要整数b本身,所以不应该关心b的依赖关系。

这里是一个例子,如果你错过了指定libb.so的依赖的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 (想到dynamic加载插件),调用也会失败。 所以"right"应该是wrong

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

典型的UNIX链接器从左到右工作,所以把所有的依赖库放在左边,那些满足链接右边那些依赖关系的库。 你可能会发现一些图书馆依赖于其他图书馆,而另外一些图书馆却依赖于它们。 这是它变得复杂的地方。 说到循环引用,修复你的代码!

下面是一个例子,以说明在涉及静态库时GCC如何与GCC协同工作。 所以我们假设我们有以下情况:

  • myprog.o – 包含main()函数,依赖于libmysqlclient
  • libmysqlclient – 静态的,为了这个例子(你更喜欢共享库,当然,因为libmysqlclient是巨大的)。 在/usr/local/lib ; 并依赖于来自libz东西
  • libz (dynamic)

我们如何链接? (注意:使用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 

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

在Qt这意味着增加:

 QMAKE_LFLAGS += -Wl,--start-group 

节省大量的时间,而且似乎不会减慢链接的速度(这比复杂程序花费的时间less得多)

您可以使用-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. 组内的顺序非常重要! 下面是一个例子:一个debugging库有一个debugging例程,但是非debugging库有一个相同的弱版本。 您必须将debugging库FIRST放入组中,否则您将parsing为非debugging版本。
  2. 您需要在组列表中的每个库之前使用-Xlinker

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

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

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

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

根据不同的连接器HP / Intel / GCC / SUN / SGI / IBM /等你可以得到无法parsing的函数/variables等,在一些平台上,你必须列出库两次。

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

一旦你find了一个解决scheme文档,那么下一个开发者就不用再重新编译它了。

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

一个简短的提示让我不知所措:如果你将链接器调用为“gcc”或“g ++”,那么使用“–start-group”和“–end-group”不会将这些选项传递给链接器 – 也不会标记一个错误。 如果库命令错误,它将会使未定义符号链接失败。

你需要把它们写成“-Wl, – start-group”等,告诉GCC把parameter passing给链接器。

链接顺序肯定是重要的,至less在一些平台上。 我看到与错误的顺序链接库的应用程序崩溃(其中错误的意思是在B之前链接,但B依赖于A)。

我想这是因为其中一些库依赖于其他库,如果他们还没有被链接,那么你会得到链接器错误。