经典的 C++ 静态初始化顺序惨败重温

The classical C++ static initialization order fiasco revisited

最近遇到一个奇怪的情况

让我们考虑以下 class(放在 header.h 中):

#ifndef HEADER_H
#define HEADER_H

#include <set>

template <class V, class T>
class Class
{
public:
    typedef std::set<const Class<V, T>* > instances_list;

    explicit Class(const V& Value):m_value(Value)
    {
    s_instances.insert(this);
    }
private:
    static instances_list s_instances;
    V m_value;
};

template <typename V, typename T>
typename Class<V,T>::instances_list Class<V,T>::s_instances;

class Something : public Class<int, Something>
{
public:
    static const Something SOMETHING_CONSTANT;

private:
    explicit Something(int value): Class<int, Something>(value)
    {}
};

#endif

以及一个使用它的非常简单的应用程序:

#include "header.h"

const Something Something::SOMETHING_CONSTANT (1);

int main()
{
}

编译它会导致不同程度的成功。

g++(4.9.2、4.8.4 和 4.3.2)编译可执行文件,但它们会产生 SEGFAULT,堆栈跟踪如下:

#0  0x00007ffff7b4aaaa in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x00000000004012bb in std::_Rb_tree_iterator<Class<int, Something> const*>::operator-- (this=0x7fffffffdcf0) at /usr/include/c++/4.8/bits/stl_tree.h:204
#2  0x0000000000400ef2 in std::_Rb_tree<Class<int, Something> const*, Class<int, Something> const*, std::_Identity<Class<int, Something> const*>, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::_M_get_insert_unique_pos (this=0x6030c0 <Class<int, Something>::s_instances>, __k=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_tree.h:1333
#3  0x0000000000400c1d in std::_Rb_tree<Class<int, Something> const*, Class<int, Something> const*, std::_Identity<Class<int, Something> const*>, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::_M_insert_unique (this=0x6030c0 <Class<int, Something>::s_instances>, __v=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_tree.h:1377
#4  0x0000000000400b19 in std::set<Class<int, Something> const*, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::insert (this=0x6030c0 <Class<int, Something>::s_instances>, 
    __x=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_set.h:463
#5  0x0000000000400ad9 in Class<int, Something>::Class (this=0x6030a4 <Something::SOMETHING_CONSTANT>, Value=@0x7fffffffde24: 1) at header.h:14
#6  0x0000000000400aa2 in Something::Something (this=0x6030a4 <Something::SOMETHING_CONSTANT>, value=1) at header.h:30
#7  0x0000000000400a24 in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at main.cpp:3
#8  0x0000000000400a6b in _GLOBAL__sub_I__ZN9Something18SOMETHING_CONSTANTE () at main.cpp:7
#9  0x00000000004015ed in __libc_csu_init ()
#10 0x00007ffff751ce55 in __libc_start_main (main=0x4009ed <main()>, argc=1, argv=0x7fffffffdf88, init=0x4015a0 <__libc_csu_init>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf78) at libc-start.c:246
#11 0x0000000000400929 in _start ()

clang(3.4.1 和 3.5.0-10)生成一个运行良好的可执行文件,没有段错误。

Visual Studio 2015 年产生了一个段错误应用程序。

如果我将所有内容都放在一个文件中,编译器会在 ideone.com (http://ideone.com/Dhh8Hl) 处找到一个运行时错误,信号 11。

我有一种感觉,这是未定义的行为......如果我不对,请纠正我。

阅读相关问题后:C++ Static member initalization (template fun inside) , Template static members initialization order and Initialization order of static data inside class template我仍然无法从标准中找到相关段落,这些段落告诉我为什么在使用 g++ 和 MSVC 编译时会失败,但通过 clang。

(3.6.2) 告诉我:

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. A reference with static storage duration and an object of POD type with static storage duration can be initialized with a constant expression (5.19); this is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.

从中我了解到Static initialization shall be performed before any dynamic initialization takes place.和我认为const Something Something::SOMETHING_CONSTANT (1); 属于常量初始化的范畴(如果我错了请纠正我)因此它是一个静态初始化。另外,上面的那个说 Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. 很好,因为我只有其中一个,但我就是不明白为什么静态模板成员没有在该类型的实际成员之前初始化。

我已经使用 https://isocpp.org/wiki/faq/ctors#static-init-order 解决了这个问题,所以现在我很好奇为什么编译器的行为如此不同,而这是正确的。

初始化

const Somthing SomeThing::SOMETHING_CONST(1);

不是 常量初始化:它初始化一个 const 但是动态地这样做,即它是动态初始化。常量初始化发生在计算 常量表达式 时。这是一个比 const 更具体的含义,并且仅适用于可以在编译期间计算的实体(有关更多详细信息,请参见第 5.19 节 [expr.const])。

如果您希望此初始化作为常量初始化发生,您需要创建构造函数 constexpr。鉴于您在此初始化期间访问了 std::set<int>,我怀疑您能否设法使您的构造函数 constexpr.

这只是使用全局对象的常见危险。如果您需要对初始化顺序进行某种程度的控制,请使用通常的 hack 来至少以合适的顺序初始化全局对象,并将它们包装到一个返回对局部静态变量的引用的函数中。或者,您 可能 能够创建类似于 std::set<int>constexpr 版本的内容,然后可用于常量初始化。