我们将代码库分解为静态库。不幸的是,这些库具有循环依赖性;例如,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被拉入两次,链接是否会因多重定义的符号而失败?或者,我们是否有任何风险可以使用相同静态对象的两个副本,从而产生微妙的错误?
基本上,我想知道多次链接同一个库是否存在链接时或运行时失败的可能性;如果是的话,如何触发它们。感谢。
答案 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
传递给链接器。为什么?
libfoo
并使用符号fooA
的定义,但没有使用fooC
的定义,因为到目前为止,还没有必要也包含{{1} }转换成二进制文件。但是,链接器开始寻找fooC
的定义,因为barB
才能正常工作。fooA
,包括-libbar
的定义(但不是 barB
),并开始寻找barD
的定义。 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 ...