【发布时间】:2018-07-15 16:37:09
【问题描述】:
据我所知,命名空间范围内的静态变量应该在每个编译单元中都有一份副本。所以如果我有这样的头文件:
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的副本。
主程序如下所示:
#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 中的完整源代码。
【问题讨论】:
-
您可以仔细查看相关部分中的2nd post。我认为底层机制是一样的。
-
欢迎来到 dll 地狱。基本上每个 dll 都将拥有与其代码一样喜欢的静态/全局变量。
-
@TheDude 那篇帖子说确实有两个副本,但主要的副本以某种方式掩盖了共享库中的副本?但是为什么链接器更喜欢这样做,这不违背对静态变量的普遍理解吗?
-
我相信这是线程安全的事情,因为共享库可以托管在不同的线程上下文中,并且静态变量实例化保证是线程安全的。但是,链接器对线程上下文一无所知。我不确定,否则我会将其发布为答案。
dlopen()是 POSIX 标准顺便说一句。 -
我想到的一个想法是,将
static变量定义放入这些 TU 中的匿名命名空间,它可以按照您期望为单独的翻译单元提供单独副本的方式工作。这些应该是那里真正的私人副本。
标签: c++ static linker shared-libraries dlopen