Search code examples
c++static-initializationstatic-order-fiasco

In C++ are simple initializations of built-in type immune to the static initialization order fiasco?


I know about the static initialization order fiasco in C++ and the construct on first use idiom to avoid it. Thus in the code below the global assignment of a may happen before that of foo::a and so the value of a is undefined. One the other hand, the global assignment of b is OK, since it invoked the function foo::b().

#include <iostream>
#include <string>

using namespace std;

// foo.hpp

class foo {
public:
  static const string a;
  static const string& b();
  static const char* const c;
  static const char* const d[2];
  static const int e;
  static const int f[2];
};

// foo.cpp

const string foo::a("astr");
const string& foo::b() {
  static const string t("bstr");
  return t;
}
const char* const foo::c = "cstr";
const char* const foo::d[2] = {"dstr1", "dstr2"};
const int foo::e = 5;
const int foo::f[2] = {6, 7};

// main.cpp

// global initializations
string a = foo::a;              // dangerous, might be "" or "astr"
string b = foo::b();            // safe, guaranteed to be "bstr"
const char* c = foo::c;         // what about these...?
const char* d = foo::d[0];
int e = foo::e;
int f = foo::f[0];

int main() {
  cout << a << " " << b << "\n"
       << c << " " << d << "\n"
       << e << " " << f << "\n";
}

(Imagine I've combined foo.hpp, foo.cpp, and main.cpp here.) However what about variables which are built-in types or arrays of them? Thus are global assignments of c, d, e, and f safe in this code? It seems possible that the linker can set the memory for these variables so no initialization needs to take place at run time. But can I rely on this?

I know I shouldn't be using global variables. However, I'm the author of a library (foo.cpp and foo.hpp) and I have no control over what a user of my library (the author of main.cpp) does.


Solution

  • The key here is the difference between "static initialization" (formally known using the language of the Standard as dynamic initialization of objects with static storage duration which has the ordering fiasco) and static initialization.

    The Standard says (section [basic.start.static]) that

    A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [ Note: Such a class may have a non-trivial destructor — end note ]

    Constant initialization is performed:

    • if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression and the reference is bound to a glvalue designating an object with static storage duration, to a temporary object or subobject thereof, or to a function;
    • if an object with static or thread storage duration is initialized by a constructor call, and if the initialization full-expression is a constant initializer for the object;
    • if an object with static or thread storage duration is not initialized by a constructor call and if either the object is value-initialized or every full-expression that appears in its initializer is a constant expression.

    If constant initialization is not performed, a variable with static storage duration or thread storage duration is zero-initialized. 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.

    Your c, d, e, and f objects have constant initializers, so their initialization is completed during the static initialization phase (even though c and d are NOT themselves constant), and their values are available during all dynamic initialization, even those that came lexically before.