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

Resolving circular dependencies by linking the same library twice?

本文关键字:解决 循环 关系 依赖 同一个 链接 两次      更新时间:2023-10-16

我们有一个分解为静态库的代码库。不幸的是,这些库具有循环依赖关系;例如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个具有复杂相互依赖性的库。)

我一直在查看我们的Makefile,将第二种形式更改为第一种形式,但现在我的同事问我为什么。。。除了"因为它更干净"和模糊地认为另一种形式有风险之外,我没有一个好的答案。

那么,多次链接同一个库是否会产生问题?例如,如果相同的.o被拉入两次,那么多重定义符号的链接会失败吗?或者,我们有没有可能最终得到同一静态对象的两个副本,从而产生微妙的错误?

基本上,我想知道多次链接同一个库是否可能导致链接时间或运行时故障;如果是,如何触发它们。谢谢

的问题

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

不能保证在libfoo上两次通过和在libbar上一次通过就足够了。

Wl,--start-group ... -Wl,--end-group的方法更好,因为它更健壮。

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

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

为了能够在上面的情况下进行构建,我们需要将-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

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

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

我所能提供的只是缺少反例。事实上,我以前从未见过第一种形式(尽管它显然更好),总是看到第二种形式解决了这个问题,因此也没有观察到问题。

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

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

由于它是一个遗留应用程序,我敢打赌库的结构是从一些可能不再重要的排列中继承的,例如用于构建另一个您不再做的产品。

即使继承的图书馆结构仍然存在结构性原因,几乎可以肯定的是,从遗留安排中再建造一个图书馆仍然是可以接受的。只需将20个库中的所有模块放入一个新库liballofthem.a即可。那么每个应用程序都是简单的g++ -o myApp -lallofthem ...