共享库 libbar.so 静态链接到 libfoo.a,程序链接到 libbar.so 和 libfoo.so,会发生什么?

Shared library libbar.so static linking to libfoo.a, program linking to libbar.so and libfoo.so, what happens?

(希望这不会变得太复杂...)

我构建了一个第三方 C++ 库 libfoo,同时具有静态 (libfoo.a) 和共享 (libfoo.so) 版本。目标文件是使用 -fPIC 创建的。 libfoo 有几个用于配置其行为的全局变量,以及依赖于这些全局变量的函数,为简单起见,函数 func1() 与变量 global_a.

交互

我已经构建了静态链接到 libfoo.a 的 C++ 共享库 libbar。这是在没有 --whole-archive 选项的情况下完成的。所以我相信 libbar.so 包括它需要的所有符号和定义 libfoo.a。该库调用 libfoo 中的函数,这些函数使用全局变量来控制行为。在这种情况下,假设它调用函数 func1().

最后,我有一个 C++ 客户端程序 baz,它动态链接到 libfoo.solibbar.so。此客户端还使用来自 libfoo 的相同 functions/global 变量。它修改 global_a 并调用 func1().

一般来说,在这种情况下,客户端 baz 的行为是什么?这种配置是我应该避免的,还是可以的?如果 libfoo.alibfoo.so 是不同的版本会怎样?

对于全局变量,我有点期待 libbar.so 中的代码会使用它自己的副本,而 baz 中的任何更改都会修改 libfoo.so 中的副本,但是那不是我所看到的。当baz修改global_a时,效果见libbar.so。据我所知,客户端 baz 没有任何其他异常行为。

我还尝试将 bazlibfoo.a 链接起来,看到了相同的(显然是正确的)行为。

无论哪种情况,Valgrind 均未显示任何错误。

In general, what will be the behavior of the client baz in this scenario?

通常,共享库在 UNIX/ELF 系统上的行为旨在模仿存档库的行为。

特别是,如果您的二进制 baz link 同时针对 libfoo.solibbar.so,两者 export global_a,则第一个定义获胜。也就是说,bazlibfoo.solibbar.soglobal_a 的所有引用都将绑定到 link 顺序中找到的第一个实例(大概是 libfoo.so 这里)。

可以通过使用例如隐藏符号来修改此行为。 -fvisibility-hidden,使用 __attribute__((visibility("hidden"))) 或 linker 脚本。

您可以通过

查看每个库导出的内容
nm -AD libfoo.so libbar.so | grep global_a

(此命令只会显示导出的符号)。

For the global variables I was sort of expecting that the code in libbar.so would use its own copies

这种期望是不正确的。

Is this sort of configuration something I should avoid, or is it OK?

这是您通常应该避免的事情,因为例如更新 libbar.so 而不重新 linking libfoo.so 可能会产生不一致。假设 global_a 的类型在后来的修订版中从 int 更改为 double。突然一个简单的语句,如:

global_a = 0.0;
libbar.so 内的

现在可能会损坏 libfoo.soglobal_a 之后的不相关变量(libfoo.so 内的 global_a 仍然是一个 4 字节 int,但现在写入了 8 个字节 double

Valgrind in either case has no shown errors.

Valgrind 在检查全局变量方面异常 薄弱,而且几乎从不抱怨它们。您应该养成使用 gcc -fsanitize=address 检查程序的习惯。