共享库中静态函数成员的销毁顺序

时间:2018-04-27 14:30:50

标签: c++ destructor static-variables

我正在探索与单身人士相关的Boost.Serialization中一个非常棘手的错误。对于上下文:Boost 1.65更改了单例的实现,打破了is_destructed通知,导致程序退出或库卸载时出现段错误。提升1.66“固定”,但泄漏记忆。

单例代码(与此问题相关)归结为:

template<class T> struct singleton{
    T& inst(){
        static T t;
        return t;
    }
}

使用静态成员函数变量可以避免static init fiasco,但仍然存在与销毁相同的问题。

然而,Finding C++ static initialization order problems显示了代码如何解决这个问题:当A的Ctor使用B时,B将首先被构造,因此最后被解构。这也在Destruction order of static objects in C++中说明。 (completion of the destructor happens in the reverse order of the completion of the constructor

到目前为止一切顺利。 Boost.Serialization现在使用多个extended_type_info_typeid<T>类型的单例来在另一个单例T中注册usertype std::multiset<const bs::typeid_system::extended_type_info_typeid_0*,...>的一些元数据。这是通过使用multiset的构造函数中的extended_type_info_typeid_0(假设来自此处的所有单例)来完成的。在extended_type_info_typeid_0的析构函数中,multiset中的条目已被删除。

这意味着我们完全符合上述情况,multiset应该比其他实例更长。

使用共享库时会出现故障。我有以下测试用例:

test_multi_singleton.cpp:

int f();
int g();

int main(int argc, char**){
  // Make sure symbols are used
  if(argc==8) return f();
  if(argc==9) return g();
}

multi_singleton1.cpp:
#include <boost/serialization/extended_type_info_typeid.hpp>

int f(){
  return 0 != boost::serialization::extended_type_info_typeid<int>::get_const_instance().get_key();
} 

multi_singleton2.cpp:
#include <boost/serialization/extended_type_info_typeid.hpp>

int g(){
  // Use different(!) type
  return 0 != boost::serialization::extended_type_info_typeid<float>::get_const_instance().get_key();
} 

Build with:

g++ multi_singleton1.cpp -lboost_serialization -fPIC -shared -olibmulti_singleton1.so
g++ multi_singleton2.cpp -lboost_serialization -fPIC -shared -olibmulti_singleton2.so
g++ test_multi_singleton.cpp -L. -lmulti_singleton1 -lmulti_singleton2

Run in valgrind:
valgrind ./a.out

可以看出,这会破坏Boost 1.65中的记忆。原因是我通过劫持和记录ctor / dtor调用来追踪乱码:

ctor 0x7f9f0aa074a0 std::multiset<const extended_type_info_typeid_0*>
ctor 0x7f9f0a7f63e0 extended_type_info_typeid<float>

ctor 0x7f9f0a7f64a0 std::multiset<const extended_type_info_typeid_0*>
ctor 0x7f9f0aa073e0 extended_type_info_typeid<int>

dtor 0x7f9f0aa073e0 extended_type_info_typeid<int>
dtor 0x7f9f0aa074a0 std::multiset<const extended_type_info_typeid_0*>
dtor 0x7f9f0a7f64a0 std::multiset<const extended_type_info_typeid_0*>
dtor 0x7f9f0a7f63e0 extended_type_info_typeid<float>

这是使用GCC 6.4,但与GCC 7.1相同。正如您所看到的那样,2个多重集合在第二个extended_type_info_typeid之前被一起销毁。

我错过了什么吗?这是否允许C ++标准?

1 个答案:

答案 0 :(得分:1)

来自basic.start.term/3

  

如果构造函数完成或动态初始化了   具有静态存储持续时间的对象强烈地发生在之前   另外,对第二个析构函数的完成进行了排序   在第一个析构函数启动之前。

此外:

  

对于数组或类类型的对象,该对象的所有子对象都是   在具有静态存储持续时间的任何块范围对象之前销毁之前的   在子对象建设期间初始化被破坏。