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

时间:2012-02-21 15:37:50

标签: c++ linux linker ld circular-dependency

我们将代码库分解为静态库。不幸的是,这些库具有循环依赖性;例如,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被拉入两次,链接是否会因多重定义的符号而失败?或者,我们是否有任何风险可以使用相同静态对象的两个副本,从而产生微妙的错误?

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

3 个答案:

答案 0 :(得分:6)

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

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

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

答案 1 :(得分:3)

问题

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的定义,因为到目前为止,还没有必要也包含{{1} }转换成二进制文件。但是,链接器开始寻找fooC的定义,因为barB才能正常工作。
  2. 链接器看到fooA,包括-libbar的定义(但不是 barB),并开始寻找barD的定义。
  3. fooC的定义是在fooC进行第二次处理时发现的。现在很明显,还需要定义libfoo-但为时已晚,命令行上再没有barD了!

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

因此使用

libbar

是一种更可靠的方法,因为链接器会根据需要在库组中进行多次传递-仅当传递未更改符号表时,链接器才会移至命令行中的下一个库。

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

答案 2 :(得分:1)

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

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