【问题标题】:Are file scoped atomics subject to the initialization order fiasco?文件范围的原子是否受初始化顺序失败的影响?
【发布时间】:2016-10-12 23:24:14
【问题描述】:

考虑以下情况,我们在不同的翻译单元中有两个文件范围的对象,这是通过初始化顺序失败的方式为未定义行为的通常设置:

a.hpp

struct thing {
public:
    thing(int value);
    ~thing();

    int value() const;

    static int count();

private:
    int _value;
};

a.cpp

#include "a.hpp"

#include <atomic>

namespace {
    std::atomic<int> things;
}

thing::thing(int value) : _value(value) {
    ++things;
}

thing::~thing() {
    --things;
}

int thing::value() const {
    return _value;
}

int thing::count() {
    return things.load();
}

b.cpp

#include <iostream>

#include "a.hpp"

namespace {
    thing static_thing(42);
}

void foo() {
    std::cout << static_thing.value() << ' ' << thing::count() << '\n';
}

此代码是否受制于 a.cpp 中范围为原子的文件 thingsb.cpp 中范围为 static_thing 的文件之间的初始化顺序失败?如果不是,为什么不呢?特别是,std::atomic 有什么特别之处,它消除了原本清晰的 init 顺序惨败?是否有可以命名的特定概念以使用静态断言来强制执行此操作?比如:

static_assert(std::is_trivial<decltype(things)>::value, "file static counter is not trivial");

如果不是std::is_trivial,是否有另一个概念和相关的类型特征可以更好地模拟这个?

相反,是否存在反初始化惨败?如果是这样,为什么,或者为什么不这样,同样的问题。

【问题讨论】:

  • 我对此事的简要研究表明,鉴于std::atomic&lt;int&gt; 必须是专门化的,并且专门化的原子具有某些属性,std::atomic&lt;int&gt; 就像一个 POD,用于初始化顺序,例如没有惨败。但是,这种情况也可以像大多数其他受 init 失败的情况一样解决:但是在函数静态范围内声明静态对象,并利用函数静态范围对象必须在第一次进入之前初始化的事实函数。
  • 静态对象在不同翻译中的构造顺序仍然未指定。使用std::atomic 不会改变这一点。你为什么会期望它?特定的编译器可能会首先实例化原子,但这不是必需的。
  • @Peter 因为 std::atomic 有一个 constexpr 构造函数,并且正在用一个常量初始化,并且有一个普通的析构函数。这表明该对象实际上是不朽的。由于静态初始化发生在任何动态初始化之前,并且由于没有什么可以破坏它,所以它基本上是不朽的。我看不出怎么会有初始化订单惨败w.r.t。任何具有动态初始化的东西。
  • 在创建对象时发生惨败 - 随后永远存在(时间结束是程序终止)是无关紧要的。根据 C++14 3.6.2/2,常量初始化(包括 constexpr)在动态初始化之前并在零初始化之后,但问题是依赖于任何两个静态对象的未指定初始化顺序。当有两个带有constexpr 构造函数的静态对象接受一个指针时,必须首先初始化两个指针,这需要构造指针。如果依赖于他们的构建顺序.....

标签: c++ c++11 static initialization undefined-behavior


【解决方案1】:

std::atomic&lt;&gt; 是一种标准布局类型,具有普通的默认构造函数和普通的析构函数。因此,它在静态初始化阶段被初始化,在动态初始化阶段之前,当全局对象的构造函数被调用时。

换句话说,这里没有发生初始化顺序惨败。

由于您没有显式初始化作用域为std::atomic&lt;int&gt; 的命名空间,因此它将被初始化为零。

§ 3.6.2 非局部变量的初始化

具有静态存储持续时间 (3.7.1) 或线程存储持续时间 (3.7.2) 的变量应在进行任何其他初始化之前进行零初始化 (8.5)。

零初始化和常量初始化合称为静态初始化;所有其他初始化都是动态初始化。静态初始化应在任何动态初始化发生之前执行。

【讨论】:

  • 这是正确的模编译器错误(请参阅影响 VS 2017 15.3 的this one
【解决方案2】:

我对 C++“初始化顺序惨败”的理解是,它仅适用于需要在运行时调用构造函数的情况。如果代码可以分解为将内存位置初始化为固定值,那么该值会像其他所有预初始化的 POD(Plain Ol' Data)一样放入“初始化数据”链接器部分(.data),也没有惨败。

我建议atomic 符合这个标准。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-25
    相关资源
    最近更新 更多