c++ - 共享库中全局静态变量的析构函数未在 dlclose 上调用

标签 c++ gcc shared-libraries dynamic-linking dlopen

在主程序中,我dlopendlclose(分别为LoadLibraryFreeLibrary)一个共享库。共享库包含一个静态变量,该变量在 dlopen 时实例化,并在 dlclose 时销毁。此行为在 MSVC 2008 和 2013、GCC 3.4.6 和 Sunstudio 12.1 上是一致的。 但是,对于 GCC 4.9.1 和 GCC 5.2.1,析构函数不再在 dlclose 上调用。相反,它在程序退出之前被调用。

静态变量类的特殊性在于,在构造函数中,有一个对返回局部static 的模板化函数get(全局作用域)的调用变量。

我能够使用以下链接到共享库的 cpp 文件重现此行为:

#include <iostream>

template <typename T> // In my actual code, i is of type T, however, this has no effect
int get()
{
   static int i = 0;
   return i;
}

class Dictionary {
public:
   Dictionary()
   {
      std::cout << "Calling Constructor" << std::endl;
      get<int>();
   }
   ~Dictionary(){
      std::cout << "Calling Destructor" << std::endl;
   }

private:
   Dictionary(const Dictionary&);
   Dictionary& operator=(const Dictionary&);
};
static Dictionary d;

我调查了可以进行的调整,以便在 dlclose 上调用析构函数,并得出以下结论:

  • 如果 get 函数没有模板化
  • 否则如果函数 get 中的变量 i 不是静态的
  • 否则如果函数 get 是静态的

主程序代码如下:

#include <dlfcn.h>
#include <cassert>
#include <string>
#include <iostream>

void* LoadLib(std::string name)
{
      void* libInstance;
      name = "lib" + name + ".so";
      libInstance = dlopen(name.c_str(), RTLD_NOW);
      if ( ! libInstance ) std::cout << "Loading of dictionary library failed. Reason: " << dlerror() << std::endl;
      return libInstance;
}

bool UnloadLib(void* libInstance)
{
     int ret = dlclose(libInstance);
     if (ret == -1)
     {
        std::cout << "Unloading of dictionary library failed. Reason: " << dlerror() << std::endl;
        return false;
     }
     return true;
}

int main()
{
   void* instance = LoadLib("dll");
   assert(instance != 0);

   assert(UnloadLib(instance));
   std::cout << "DLL unloaded" << std::endl;
}

我使用以下命令构建了二进制文件:

g++ -m64 -g -std=c++11 -shared -fPIC dll.cpp -o libdll.so
g++ -m64 -g -std=c++11 -ldl main.cpp -o main.out

在程序退出前调用析构函数时得到的输出如下:

Calling Constructor
DLL unloaded
Calling Destructor

在 dlclose 上调用析构函数时得到的输出如下:

Calling Constructor
Calling Destructor
DLL unloaded

问题:

  • 如果 GCC 版本之间的行为变化不是错误,您能否解释一下为什么不在 dlclose 上调用析构函数?
  • 您能否解释一下每项调整:为什么在这种情况下会调用 dlclose 的析构函数?

最佳答案

不能保证卸载(调用析构函数)发生在 dlclose 上。在 musl (与 glibc 相反),构造函数仅在库第一次运行时运行,而析构函数仅在退出时运行。对于可移植代码,不能假定 dlclose 会立即卸载符号。

卸载行为取决于 glibc 在进行动态链接时的符号绑定(bind),并且独立于 GCC。

静态变量 get::i 有一个 STB_GNU_UNIQUE 绑定(bind)。对于内联函数中的静态变量,对象的唯一性由 ELF 链接器保证。但是,对于动态加载,动态链接器通过标记符号 STB_GNU_UNIQUE 来确保唯一性。因此,通过其他代码尝试 dlopen 同一个共享库的另一次尝试将查找符号并发现它是唯一的,并从唯一符号表中返回存在的符号。无法卸载具有唯一绑定(bind)的符号。

如果不需要,可以使用 -fno-gnu-unique 禁用唯一绑定(bind)。

引用资料

Bug that I raised to GCC

STB_GNU_UNIQUE

关于c++ - 共享库中全局静态变量的析构函数未在 dlclose 上调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38510621/

相关文章:

.net - 将 C++ POS 控件与 .NET POS SDK 连接时出现问题

c++ - 使用 clang 进行源到源转换(最先进的技术)

java - 无法读取通过 .a 实现 JNI 的 .so 和 Ubuntu x64、Netbeans IDE 上的主应用程序

c - 我收到错误 "warning: initialization makes pointer from integer without a cast [enabled by default]"

c++ - 如何在 C++ 中将常量映射指针初始化为空?

c - 使用可用的堆栈保护器 SSP 搜索 GCC 版本

c++ - Qt 无法将 OCR Tesseract 与 OpenCV 3.2 Ubuntu 一起使用

c - 将库路径导出到 LD_LIBRARY_PATH 和在链接时使用 -L 标志有什么区别?

java - xpath 2.0可以在java中使用吗?

c++ - 编译 Cilk Plus 程序时遇到问题