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

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

Why does the order in which libraries are linked sometimes cause errors in GCC?

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


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

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

1
2
3
4
5
6
7
8
9
10
11
12
$ cat a.cpp
extern int a;
int main() {
  return a;
}

$ cat b.cpp
extern int b;
int a = b;

$ cat d.cpp
int b;

链接到静态库

1
2
3
4
5
6
7
8
9
$ 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 -)(您可能需要转义parens,例如-\(-\))。然后,链接器会多次搜索这些封闭的lib,以确保解析循环依赖关系。或者,您可以多次指定库,因此每个库都在彼此之前:-la -lb -la

链接到动态库

1
2
3
4
5
6
7
$ 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的依赖关系,这是一个含义的示例

1
2
3
4
5
6
7
8
9
10
$ 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


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

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


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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上这意味着添加:

1
QMAKE_LFLAGS += -Wl,--start-group

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


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

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

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


您可以使用-Xlinker选项。

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

几乎等于

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

小心!

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

  • 一个快速的提示让我感到高兴:如果您将链接器调用为"gcc"或"g ++",那么使用"--start-group"和"--end-group"将不会将这些选项传递给 链接器 - 也不会标记错误。 如果您的库顺序错误,它将使未定义符号的链接失败。

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


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

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

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

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

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


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


    推荐阅读

      linux链接远程命令?

      linux链接远程命令?,系统,地址,网络,密码,软件,名称,工具,服务,电脑,认证,如

      linux动态链接库命令?

      linux动态链接库命令?,代码,项目,工程,电脑,网上,文件,程序,静态,命令,目录,

      linux编辑文本命令行?

      linux编辑文本命令行?,工作,系统,信息,状态,地址,命令,管理,标准,目录,文件,L

      linux命令忽略错误?

      linux命令忽略错误?,系统,地址,工作,信息,设备,命令,设计,灵活,观察,标准,lin

      linux查看文本的命令?

      linux查看文本的命令?,系统,工作,标准,信息,命令,管理,数据,文件,目录,时间,L

      linux文本中插入命令?

      linux文本中插入命令?,工作,地址,系统,命令,信息,第一,工具,地方,密码,情况,L

      linux文本撤销命令?

      linux文本撤销命令?,系统,命令,信息,环境,状态,进程,程序,终端,快捷键,用户,

      linux命令中创建文本?

      linux命令中创建文本?,系统,时间,文件,终端,名字,名称,发行,命令,文件夹,文

      linux删除硬链接命令?

      linux删除硬链接命令?,工作,数据,系统,信息,链接,命令,设备,名称,不了,概念,l

      linux文本编辑命令?

      linux文本编辑命令?,工作,系统,地址,信息,环境,基础,命令,入口,网站,技术,lin

      linux创建软链接命令?

      linux创建软链接命令?,工作,地址,位置,系统,信息,管理,服务,名字,链接,文件,

      查找文本的linux命令?

      查找文本的linux命令?,工具,命令,信息,系统,标准,文件,终端,情况,内容,字符

      linux中软链接命令?

      linux中软链接命令?,位置,设备,数据,信息,系统,文件,链接,地址,对比,源文件,L

      linux命令链接网页?

      linux命令链接网页?,网络,信息,系统,网址,工具,网站,状态,发行,数据,命令,Lin

      linux软链接命令用法?

      linux软链接命令用法?,时间,系统,位置,服务,链接,数据,地址,基础,不了,信息,L

      linux文本下的命令?

      linux文本下的命令?,地址,工作,系统,标准,信息,命令,数据,目录,文件,控制台,L

      linux目录链接命令?

      linux目录链接命令?,系统,工作,时间,地址,数据,信息,管理,目录,文件,命令,请

      linux命令创建硬链接?

      linux命令创建硬链接?,数据,链接,系统,文件,位置,概念,不了,源文件,目录,命

      linux命令行文本比较?

      linux命令行文本比较?,时间,系统,标准,状态,代码,设备,工具,软件,文件,命令,l

      linux复制链接命令?

      linux复制链接命令?,系统,认证,文件,命令,目录,源文件,选项,目标,功能,语法,L