重温经典的C ++静态初始化顺序 [英] The classical C++ static initialization order fiasco revisited

查看:159
本文介绍了重温经典的C ++静态初始化顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近遇到了一个奇怪的情况.

I have encountered a strange situation recently.

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

Let's consider the following class (place in 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

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

and a very simple application using it:

#include "header.h"

const Something Something::SOMETHING_CONSTANT (1);

int main()
{
}

编译它会带来不同程度的成功.

Compiling it results in various degrees of successfulness.

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

g++ (4.9.2, 4.8.4 and 4.3.2) compiles an executable, but they produce a SEGFAULT, with a stack trace like:

#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)产生的可执行文件运行良好,不会出现段错误.

clang (3.4.1 and 3.5.0-10) produce an executable which runs nicely, does not segfault.

Visual Studio 2015生成一个段错误应用程序.

Visual Studio 2015 produces a segfaulting application.

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

If I put everything in one file the compiler found at ideone.com (http://ideone.com/Dhh8Hl) produces a runtime error, signal 11.

我有这种感觉,这是不确定的行为……如果我做错了,请纠正我.

I have the feeling, this is undefined behaviour ... Please correct me if I'm not right.

阅读相关问题后: C ++静态成员初始化(内部模板有趣)模板静态成员初始化顺序

After reading relevant questions: C++ Static member initalization (template fun inside) , Template static members initialization order and Initialization order of static data inside class template I am still unable to find relevant paragraphs from the standard which tell me why does this fail when compiled with g++ and MSVC but passes on clang.

(3.6.2)告诉我:

The (3.6.2) tells me:

具有静态存储持续时间(3.7.1)的对象应进行零初始化 (8.5)在进行任何其他初始化之前.参考与 静态存储期限和带有静态存储的POD类型的对象 持续时间可以用常量表达式(5.19)初始化;这是 称为常量初始化.一起进行零初始化和 常量初始化称为静态初始化;所有其他 初始化是动态初始化.静态初始化应 在任何动态初始化发生之前执行.动态的 对象的初始化是有序的还是无序的. 明确专门的类模板静态数据的定义 成员已下令初始化.其他类模板静态数据 成员(即隐式或显式实例化的专长) 具有无序的初始化.命名空间中定义的其他对象 范围已命令初始化.单个对象内定义的对象 转换单元和有序初始化应被初始化 按照它们在翻译单元中定义的顺序.命令 对于未排序的对象,未指定初始化的数量 初始化以及在不同翻译单元中定义的对象.

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.很好,因为我只有一个,但是我看不出为什么静态模板成员没有在该类型的实际成员之前初始化.

and from it I understand that Static initialization shall be performed before any dynamic initialization takes place. and in my opinion const Something Something::SOMETHING_CONSTANT (1); falls in the category of constant initialization (please correct me if I'm wrong) thus it is a static initialization. Also, the one above says that Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. which is fine, since I have only one of those, but I just cannot see why the static template member is not initialized before the actual member of that type.

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

I have solved the problem using https://isocpp.org/wiki/faq/ctors#static-init-order so now I am just curious why there is so different behaviour from the compilers, and which is correct.

推荐答案

初始化

const Somthing SomeThing::SOMETHING_CONST(1);

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

is not constant initialization: it initializes a const but does so dynamically, i.e., it is dynamic initialization. Constant initialization happens when computing a constant expression. That's a more specific meaning than just const and only applies to entities which can be computed during compilation (see section 5.19 [expr.const] for more details).

如果您希望此初始化作为常量初始化发生,则需要使构造函数成为constexpr.假设您在初始化期间访问std::set<int>,我怀疑您将设法使构造函数为constexpr.

If you want this initialization to happen as constant initialization you need to make your constructor constexpr. Given that you access a std::set<int> during this initialization I doubt that you'll manage to make your constructor constexpr.

这只是使用全局对象的常见危险.如果您需要某种程度的初始化顺序控制,请使用通常的技巧,使全局对象至少按照适当的顺序进行初始化,然后将它们包装到返回对本地静态变量的引用的函数中.另外,您也许可以创建类似于std::set<int>constexpr版本的东西,然后将其用于常量初始化.

This is just the usual peril of using global objects. If you need some level of control of the initialization order use the usual hack to get the global objects at least initialized in a suitable order and wrap them into a function returning a reference to a local static variable. Alternatively you may be able to create something akin to a constexpr version of a std::set<int> which then could be used for constant initialization.

这篇关于重温经典的C ++静态初始化顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆