【问题标题】:Order of destruction for static function members in shared libraries共享库中静态函数成员的破坏顺序
【发布时间】:2018-10-08 10:19:15
【问题描述】:

我目前正在探索 Boost.Serialization 中与单例相关的一个非常棘手的错误。对于上下文:Boost 1.65 更改了打破is_destructed 通知的单例的实现,这会导致程序退出或库卸载时出现段错误。 Boost 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&lt;T&gt; 类型的单例将用户类型T 的一些元数据注册到另一个单例std::multiset&lt;const bs::typeid_system::extended_type_info_typeid_0*,...&gt; 中。这是通过使用extended_type_info_typeid_0 的构造函数中的multiset(假设这里的所有单例)来完成的。在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 个多重集在第 2 个 extended_type_info_typeid 之前被一起销毁。

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

【问题讨论】:

  • 解决这个问题(以及许多其他相关问题):不要使用单例。它们是一种模式,往往会导致比它们解决的问题更多的问题。
  • 标准没有说明动态加载库。

标签: c++ destructor static-variables


【解决方案1】:

来自basic.start.term/3

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

还有:

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

【讨论】:

  • 这有什么帮助?不应该在第二个 type_info 之前销毁第二个地图。
猜你喜欢
  • 2011-12-24
  • 2010-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多