通过将同一个库链接两次来解决循环依赖关系?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过将同一个库链接两次来解决循环依赖关系?相关的知识,希望对你有一定的参考价值。

我们将代码库分解为静态库。不幸的是,这些库具有循环依赖性;例如,libfoo.a依赖于libbar.a,反之亦然。

我知道处理这个问题的“正确”方法是使用链接器的--start-group--end-group选项,如下所示:

g++ -o myApp -Wl,--start-group -lfoo -lbar -Wl,--end-group

但在我们现有的Makefile中,问题通常是这样处理的:

g++ -o myApp -lfoo -lbar -lfoo

(想象一下,这扩展到约20个具有复杂相互依赖性的库。)

我一直在通过我们的Makefiles将第二种形式更改为第一种形式,但现在我的同事们问我为什么......除了“因为它更清洁”以及另一种形式存在风险的模糊感,我不这样做有一个很好的答案。

那么,可以多次链接同一个库会产生问题吗?例如,如果相同的.o被拉入两次,链接是否会因多重定义的符号而失败?或者是否有任何风险,我们可以结束相同的静态对象的两个副本,创建微妙的错误?

基本上,我想知道链接时间或运行时失败是否有可能多次链接同一个库;如果是的话,如何触发它们。谢谢。

答案

我能提供的只是缺乏反例。我以前从未见过第一种形式(尽管它显然更好),并且总是看到这种形式用第二种形式解决,并且没有观察到问题。

即便如此,我仍然建议改为第一种形式,因为它清楚地显示了库之间的关系,而不是依赖于链接器以特定方式运行。

也就是说,我建议至少考虑是否有可能重构代码以将常见的部分提取到其他库中。

另一答案

这个问题

g++ -o myApp -lfoo -lbar -lfoo

是没有保证,两次通过libfoo和一次通过libbar就足够了。

使用Wl,--start-group ... -Wl,--end-group的方法更好,因为更强大。

请考虑以下场景(所有符号都在不同的对象文件中):

  • myApp需要在fooA中定义的符号libfoo
  • 符号fooA需要在barB中定义的符号libbar
  • 符号barB需要在fooC中定义的符号libfoo。这是循环依赖,可以由-lfoo -lbar -lfoo处理。
  • 符号fooC需要在barD中定义的符号libbar

为了能够在上面的情况下构建,我们需要将-lfoo -lbar -lfoo -lbar传递给链接器。为什么?

  1. 链接器第一次看到libfoo并使用符号fooA但不是fooC的定义,因为到目前为止它没有必要将fooC也包含在二进制文件中。然而,链接器开始寻找barB的定义,因为它的定义是fooA运行所必需的。
  2. 链接器看到-libbar,包括barB(但不是barD)的定义,并开始寻找fooC的定义。
  3. fooC的定义可以在libfoo中找到,当它第二次处理时。现在很明显,也需要barD的定义 - 但为时已晚,命令行上没有libbar了!

上面的例子可以扩展到任意的依赖深度(但这在现实生活中很少发生)。

因此使用

g++ -o myApp -Wl,--start-group -lfoo -lbar -Wl,--end-group

是一种更健壮的方法,因为链接器根据需要经常在库组上传递 - 只有当传递没有更改符号表时,链接器才会移动到命令行上的下一个库。

然而,要付出的性能损失很小:在第一个例子中,与手动命令行-lbar相比,-lfoo -lbar -lfoo再次被扫描。不确定是否值得一提/思考。

另一答案

由于它是一个遗留应用程序,我敢打赌,库的结构继承了一些可能无关紧要的安排,比如用于构建你不再做的另一个产品。

即使仍有结构原因仍然存在于继承的库结构中,几乎可以肯定,从传统的安排中再构建一个库仍然是可以接受的。只需将20个库中的所有模块放入一个新的库liballofthem.a中。然后每个应用程序只是g++ -o myApp -lallofthem ...

以上是关于通过将同一个库链接两次来解决循环依赖关系?的主要内容,如果未能解决你的问题,请参考以下文章

为啥将库添加到链接器命令行两次?

使用循环片段依赖关系模块化单活动Android应用程序

如何在一种方法中调用一个函数两次来编译 cuda 代码?

Cmake 库依赖关系的传递

进程控制---调用fork() 两次来避免僵尸进程的产生

从另一个提供循环依赖的模块打开活动