1

在我的 linux 机器上,我有 2 个库:

libfoo1.a and libfoo2.a

它们都包含一个实现

void foo(int)

我的主程序调用 foo:

int main() { foo(1); return 0; }

我使用 g++ 以两种方式编译了程序

g++ main.cpp libfoo1.a libfoo2.a -o a1.out
g++ main.cpp libfoo2.a libfoo1.a -o a2.out

当我运行程序时,a1 显然使用了 libfoo1.a 中的 foo() 实现,而 a2 显然使用了 libfoo2。也就是说,g++ 链接了它首先看到的任何 foo() 。

我的问题(最后)是,这种“贪婪”链接策略实际上是在 C++ 标准中指定的吗?或者不同的编译器/平台会以实现定义的方式表现不同吗?

PS:把这个问题放在实际的上下文中,我真的很喜欢这个 g++ 示例的工作方式。在我的实际应用程序中,我有一个旧版 libfoo2,它实现了许多(很多!)功能,但我想在 libfoo1 中为其中的少数提供新的实现。一方面,我可以在 libfoo1 中编写一个全新的接口,实现我的少数,然后将其余部分委托给 libfoo2。但是,如果我可以依靠链接器为我完成它(即使对于像 icc 这样的非 g++ 编译器),我宁愿避免编写所有委托代码。

PPS:把它放在真正的实际上下文中,libfoo2 是 blas,而 libfoo1 是它的一些例程的自制 OpenMP 实现。我还没准备好为 MKL 掏腰包。ATLAS 不会对我要调用的函数进行多线程处理。它非常擅长多线程 GEMM,但我需要一些来自 LAPACK 的更奇特的例程也很快(zsptrf / zsptrs / zspr)。看来,我对这些例程的缓存无知 OpenMP 实现比它的缓存调整顺序实现做得更好。

对不起,帖子的长度。

4

4 回答 4

3

该标准没有说明链接顺序。我会说依赖编译器使用的任何顺序都不是一个好习惯。

于 2009-12-18T15:07:30.693 回答
3

几乎任何链接器都会像您所描述的那样运行。

链接器的传统行为是在命令行指定的库中从左到右搜索外部函数。这意味着包含函数定义的库应该出现在使用它的任何源文件或目标文件之后。

来自GCC 简介

于 2009-12-18T15:12:46.160 回答
2

根据标准,您对同一个函数有两个定义,这违反了一个定义规则。结果是未定义的行为。

处理此问题的“正确”方法可能是获取原始存档并从中提取所有目标文件。然后将新的目标文件复制到目录中,并创建一个新库,其中只包含您想要的目标文件的版本。最后,与您的组合库链接(并希望原始库中的任何内容都不依赖于您替换的任何函数的任何未记录的内容)。

于 2009-12-18T16:03:10.843 回答
1

据我所知,C++ 标准不涉及链接器。这就是为什么,例如,mingw32 和微软的 C++ 编译器可以避免使用不同的名称修饰方案。

于 2009-12-18T15:14:46.450 回答