为什么主可执行文件和 dlopen 加载的共享库共享命名空间静态变量的一个副本?
Why the main executable and a shared library loaded by dlopen share one copy of a namespace static variable?
据我了解,命名空间范围静态变量在每个编译单元中应该有一个副本。因此,如果我有一个这样的头文件:
class BadLad {
public:
BadLad();
~BadLad();
};
static std::unique_ptr<int> sCount;
static BadLad sBadLad;
和坏人.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
,只有插件.cpp包括badlad.h
,所以我希望 libPlugin.so 有一份sCount
。 - 我创建了一个链接 libBadLad.so 的主程序,我希望有 一份主
sCount
。
主程序如下所示:
#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;
}
执行主程序时,我可以看到在调用主程序之前首先创建并设置为 1sCount
变量,这是预期的。但是,在调用第一个dlopen
后,sCount
递增到 2,随后在调用dlclose
时减少到 1。同样的情况也发生在第二个 dlopen/dlclose。
所以我的问题是,为什么只有一个副本的sCount?为什么链接器不将副本分开(我认为这是大多数人所期望的)?如果我直接将 libPlugin.so 链接到 main 而不是 dlopen,它的行为是一样的。
我正在使用clang-4(clang-900.0.39.2)在macOS上运行它。
编辑:请参阅此存储库中的完整源代码。
(迭代 2)
在你的案例中发生的事情非常有趣,也非常不幸。让我们一步一步地分析它。
- 您的程序与
libBadLad.so
链接。因此,此共享库在程序启动时加载。静态对象的构造函数在main
之前执行。 - 然后,您的程序将打开
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
为什么会有分段错误?这是我们古老的静态初始化顺序惨败。故事的寓意是什么?静态变量是邪恶的。
相关文章:
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- 在为LINUX创建共享库时,如何避免STL的私有/弱副本
- 为什么构建目录中新构建的共享库与安装目录中的副本具有不同的依赖项集?
- 将相同共享指针的副本存储在不同的向量中是否是一种好的做法?
- 是否可以在并行区域中为共享 2D 数组创建选定元素的线程本地副本?(共享,私有,障碍:OPenMP)
- 提升线程问题,当一个线程与另一个线程没有相同的副本时如何共享变量?
- 共享内存中的多索引副本
- 在实例副本之间共享的类成员
- MSVC 2017 在共享库中创建模板函数的副本
- 如何在每个共享库中使用自己的静态库副本
- 为什么主可执行文件和 dlopen 加载的共享库共享命名空间静态变量的一个副本?
- 在派生类之间复制共享变量(浅副本就足够了)
- C 模板功能,相同类型,多个实例:共享一个相同的代码副本?即使在不同的CPP/对象文件中
- 混合共享/静态库时静态成员的多个副本
- C++继承,是否可以只有共享祖父类的 1 个副本
- 我能知道调用方是否保留了共享指针的副本吗?
- 创建 boost::interprocess 共享内存对象的非共享副本
- Linux共享库中全局变量的单个副本
- gcc共享ptr副本分配实现
- cudaMemcpy2D 用于共享内存副本