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

C++ 在dlclose上未调用共享库中全局静态变量的析构函数,c++,gcc,shared-libraries,dynamic-linking,dlopen,C++,Gcc,Shared Libraries,Dynamic Linking,Dlopen,在主程序中,Idlopen和dlclose(LoadLibrary和freebrary分别)是一个共享库。共享库包含一个静态变量,该变量在dlopen时实例化,在dlclose时销毁。这种行为在MSVC 2008和2013、GCC 3.4.6和Sunstudio 12.1中是一致的。 然而,对于GCC4.9.1和GCC5.2.1,析构函数不再在dlclose上调用。相反,它是在程序退出之前调用的 静态变量类的特殊性在于,在构造函数中,有一个对模板函数get(全局范围)的调用,该函数返回一个局部静

在主程序中,I
dlopen
dlclose
LoadLibrary
freebrary
分别)是一个共享库。共享库包含一个静态变量,该变量在
dlopen
时实例化,在
dlclose
时销毁。这种行为在MSVC 2008和2013、GCC 3.4.6和Sunstudio 12.1中是一致的。 然而,对于GCC4.9.1和GCC5.2.1,析构函数不再在
dlclose
上调用。相反,它是在程序退出之前调用的

静态变量类的特殊性在于,在构造函数中,有一个对模板函数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;
在程序退出之前调用析构函数时得到的输出如下:

#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;
}
Calling Constructor
DLL unloaded
Calling Destructor
Calling Constructor
Calling Destructor
DLL unloaded
在dlclose上调用析构函数时得到的输出如下:

#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;
}
Calling Constructor
DLL unloaded
Calling Destructor
Calling Constructor
Calling Destructor
DLL unloaded
问题:

  • 如果GCC版本之间的行为变化不是一个bug,您能解释一下为什么没有在dlclose上调用析构函数吗
  • 你能解释一下每一个调整吗:为什么在这种情况下在dlclose上调用析构函数

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

      卸载行为取决于执行动态链接时glibc的符号绑定,并且独立于GCC

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

      如果不需要,可以使用
      -fno gnu Unique
      禁用唯一绑定

      参考资料


      <> P> < /P>听起来像是一个错误报告,你应该对GCCSIZON变量进行线程安全(它们必须是由于C++ 11中引入的C++标准更改)。这可能会有所不同。Visual Studio 2013没有实现此功能,而2015实现了此功能。也许你应该在2015年进行测试。只有GCC4.x和更高版本实现了这一点,因此,您应该了解静态变量线程需求是否发挥了作用。