Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are file scoped atomics subject to the initialization order fiasco?

Consider the following, where we have two file scoped objects in different translation units, which is the usual setup for undefined-behavior by way of the initialization order fiasco:

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';
}

Is this code subject to an initialization order fiasco between the file scoped atomic things in a.cpp and the file scoped static_thing in b.cpp? If not, why not? In particular, what is special about std::atomic that removes what would otherwise be a clear init order fiasco? Is there a particular concept that can be named to enforce this with a static assert? Something like:

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

If not std::is_trivial, is there another concept and associated type trait that better models this?

Conversely, is there a de-initialization fiasco? Same questions about if so, why, or why not.

like image 357
acm Avatar asked Jun 12 '16 13:06

acm


2 Answers

std::atomic<> is a standard layout type with trivial default constructors, and trivial destructors. Therefore, it is initialized at static initialization phase, before the dynamic initialization phase when constructors of global objects get called.

In other words, no initialization order fiasco happens here.

Since you do not explicitly initialize the namespace scoped std::atomic<int>, it is going to be zero-initialized.

§ 3.6.2 Initialization of non-local variables

Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place.

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.

like image 92
Maxim Egorushkin Avatar answered Oct 18 '22 19:10

Maxim Egorushkin


My understanding of the C++ "initialisation order fiasco" is that it only applies when constructors need to be called at runtime. If the code can devolve down into the initialisation of a memory location to a fixed value, then that value gets put in the "initialised data" linker section (.data) like every other pre-initialised POD (Plain Ol' Data), and there is no fiasco.

I'd suggest that an atomic meets that very criterion.

like image 30
John Burger Avatar answered Oct 18 '22 19:10

John Burger