据我所知,命名空间作用域静态变量在每个编译单元中应该有一个拷贝。所以如果我有这样的头文件:
class BadLad {
public:
BadLad();
~BadLad();
};
static std::unique_ptr<int> sCount;
static BadLad sBadLad;
和 badlad.cpp
#include "badlad.h"
BadLad::BadLad() {
if (!sCount) {
sCount.reset(new int(1));
std::cout<<"BadLad, reset count, "<<*sCount<<std::endl;
}
else {
++*sCount;
std::cout<<"BadLad, "<<*sCount<<std::endl;
}
}
BadLad::~BadLad() {
if (sCount && --*sCount == 0) {
std::cout<<"~BadLad, delete "<<*sCount<<std::endl;
delete(sCount.release());
}
else {
std::cout<<"~BadLad, "<<*sCount<<std::endl;
}
}
我希望 sCount 和 sBadLad 在包含 badlad.h 的每个 cpp 文件中都是唯一的。
然而,我在下面的实验中发现情况并非如此:
- 我将 badlad 编译为共享库
libBadLad.so
。 - 我创建了另一个共享库
libPlugin.so
链接libBadLad.so
,只有plugin.cpp包含badlad.h
,所以我期待 libPlugin.so 中有一份sCount
- 我创建了一个链接 libBadLad.so 的主程序,我希望有
一份
sCount
在 main.
主程序如下所示:
#include <dlfcn.h>
int main() {
void* dll1 = dlopen("./libplugin.so", RTLD_LAZY);
dlclose(dll1);
void* dll2 = dlopen("./libplugin.so", RTLD_LAZY);
dlclose(dll2);
return 0;
}
在执行主程序时,我可以看到 sCount
变量在调用 main 之前首先创建并设置为 1,这是预期的。但是在调用第一个 dlopen
之后,sCount
增加到 2,随后在调用 dlclose
时减少到 1。第二个 dlopen/dlclose 也是如此。
所以我的问题是,为什么只有一份 sCount?为什么链接器不将拷贝分开(我认为这是大多数人所期望的)?如果我直接将 libPlugin.so 链接到 main 而不是 dlopen,它的行为是一样的。
我在 macOS 上使用 clang-4 (clang-900.0.39.2) 运行它。
编辑:请参阅 this repo 中的完整源代码.
最佳答案
(迭代 2)
发生在你身上的事很有趣也很不幸。下面我们一步一步来分析。
- 您的程序链接到
libBadLad.so
。因此,该共享库会在程序启动时加载。静态对象的构造函数在main
之前执行。 - 你的程序然后 dlopens
libplugin.so
。然后加载此共享库,并执行静态对象的构造函数。 libplugin.so
链接到的libBadLad.so
怎么样?由于该进程已经包含libBadLad.so
的图像,不会第二次加载此共享库。libplugin.so
也可以完全不链接它。- 回到
libplugin.so
的静态对象。其中有两个,sCount
和sBadLad
。 两者都是按顺序构建的。 sBadLad
有一个用户定义的非内联构造函数。 它没有在libplugin.so
中定义,所以它是针对已经加载的libBadLad.so
解析的,它定义了这个符号。BadLad::BadLad
从libBadLad.so
被调用。- 此构造函数引用静态变量
sCount
。 这解析为来自libBadLad.so
的sCount
,而不是来自libplugin.so
的sCount
,因为函数本身在libBadLad.so
中。这已经初始化并指向一个值为 1 的int
。 - 计数递增。
- 与此同时,来自
libplugin.so
的sCount
静静地坐着,被初始化为nullptr
。 - 库被卸载并再次加载等。
故事的寓意是什么? 静态变量是邪恶的。避免。
请注意,C++ 标准对此没有任何规定,因为它不处理动态加载。
然而,无需任何动态加载也可以重现类似的效果。
// foo.cpp
#include "badlad.h"
// bar.cpp
#include "badlad.h"
int main () {}
构建和测试:
# > g++ -o test foo.cpp bar.cpp badlad.cpp
./test
BadLad, reset count to, 1
BadLad, 2
BadLad, 3
~BadLad, 2
Segmentation fault
为什么会出现段错误?这是我们很好的旧静态初始化命令惨败。这个故事的主旨? 静态变量是邪恶的。
关于c++ - 为什么主可执行文件和 dlopen 加载的共享库共享一个命名空间静态变量的拷贝?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48629063/