首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么dlopen加载的主可执行文件和共享库共享名称空间静态变量的一个副本?

为什么dlopen加载的主可执行文件和共享库共享名称空间静态变量的一个副本?
EN

Stack Overflow用户
提问于 2018-02-05 18:23:35
回答 1查看 422关注 0票数 4

就我所理解的名称空间范围而言,静态变量应该在每个编译单元中有一个副本。因此,如果我有这样的头文件:

代码语言:javascript
复制
class BadLad {                                                                                      
public:                                                                                             
    BadLad();                                                                                       
    ~BadLad();                                                                                      
};                                                                                                  

static std::unique_ptr<int> sCount;                                                                 
static BadLad sBadLad;

和badlad.cpp

代码语言:javascript
复制
#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的主程序,我希望main中有一个sCount的副本。

主程序如下所示:

代码语言:javascript
复制
#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,它的行为也是一样的。

我正在使用clang-4 (clang-900.0.39.2)在macOS上运行这个程序。

编辑:请参阅这个回购的完整源代码。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-02-07 08:03:53

(迭代2)

在你的情况下发生的事情是非常有趣和非常不幸的。让我们一步一步地分析。

  1. 您的程序是针对libBadLad.so链接的。因此,在程序启动时加载此共享库。静态对象的构造函数在main之前执行。
  2. 然后,您的程序将打开libplugin.so。然后加载这个共享库,并执行静态对象的构造函数。
  3. 那么与libBadLad.so相关联的libplugin.so呢?由于进程已经包含了libBadLad.so的映像,所以这个共享库不会在第二次加载时加载。libplugin.so也可以完全不链接到它。
  4. 返回到libplugin.so的静态对象。其中有两个,sCountsBadLad。两者都是按顺序建造的。
  5. sBadLad有一个用户定义的非内联构造函数.它不是在libplugin.so中定义的,所以它是根据已经加载的libBadLad.so解析的,后者定义了这个符号。
  6. BadLad::BadLad是从libBadLad.so调用的。
  7. 此构造函数引用静态变量sCount。这将从libBadLad.so__解析为sCount,而不是从libplugin.so解析为sCount,因为函数本身位于libBadLad.so中。这已经初始化,并指向值为1的int
  8. 计数增加。
  9. 与此同时,sCountlibplugin.so静静地坐着,被初始化为nullptr
  10. 库再次被卸载和加载,等等。

这个故事的寓意是?静态变量是邪恶的。避免。

注意,C++标准对此没有什么可说的,因为它不处理动态加载。

然而,一种类似的效果可以在没有任何运动厌恶的情况下再现。

代码语言:javascript
复制
   // foo.cpp
   #include "badlad.h"

   // bar.cpp
   #include "badlad.h"
   int main () {}

构建和测试:

代码语言:javascript
复制
   # > g++ -o test foo.cpp bar.cpp badlad.cpp
   ./test
   BadLad, reset count to, 1
   BadLad, 2
   BadLad, 3
   ~BadLad, 2
   Segmentation fault

为什么分割错误?这是我们以前的静态初始化命令失败了。故事的寓意?静态变量是邪恶的。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48629063

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档