【问题标题】:The classical C++ static initialization order fiasco revisited经典 C++ 静态初始化顺序惨败重温
【发布时间】:2016-02-28 01:48:45
【问题描述】:

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

让我们考虑以下类(放在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 和 Initialization order of static data inside class template 我仍然无法从标准中找到相关段落,这些段落告诉我为什么使用 g++ 和 MSVC 编译时会失败,但会通过 clang。

(3.6.2) 告诉我:

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

从中我了解到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 解决了这个问题,所以现在我很好奇为什么编译器的行为如此不同,这是正确的。

【问题讨论】:

  • 这不是 POD(它有一个构造函数),因此这不是静态初始化。

标签: c++ templates static static-initialization


【解决方案1】:

初始化

const Somthing SomeThing::SOMETHING_CONST(1);

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

如果您希望此初始化作为常量初始化发生,您需要使您的构造函数constexpr。鉴于您在此初始化过程中访问了 std::set&lt;int&gt;,我怀疑您是否会设法使您的构造函数 constexpr。

这只是使用全局对象的常见危险。如果您需要对初始化顺序进行某种程度的控制,请使用通常的技巧来获取至少以合适的顺序初始化的全局对象,并将它们包装到返回对局部静态变量的引用的函数中。或者,您也许可以创建类似于std::set&lt;int&gt; 的constexpr 版本的东西,然后可用于常量初始化。

【讨论】:

  • 非常量但动态初始化的好消息!不幸的是,我无法访问 c++11,所以我不得不忍受这个变体。
  • Dietmar,是否可以概括任何时候静态访问非本地静态对象(无论是命名空间范围、文件范围还是外部),我必须防范 SIOF?例如,考虑在 1.cpp 中定义的函数 f() 被调用以初始化 2.cpp 中的静态对象 var,并假设 f() 返回一个 非外部 const @ 987654334@(即k在1.cpp中定义与f()相同)。
  • 在我上面的帖子中添加:让const int k = 99 让我们清楚k 本身不易发生SIOF。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-15
  • 1970-01-01
相关资源
最近更新 更多