C++ allows global variables to have arbitrally complex initializers.
Such variables are zero-initialized statically, and further initialization is performed at runtime, in the process referred to as dynamic
initialization.
The dynamic initialization of such variables is performed in the order of their definitions within a single source file. However, the order of
initialization of variables defined in different source files is not specified. Therefore, when a global variable refers to a variable defined in a
different source file, the behavior of the program is not guaranteed:
extern int global1;
// Defined in different source file as:
// int global1 = 20;
int const global2 = global1 + 10; // Noncompliant
Depending on the order of initialization of global1
and global2
, global2
may be set to:
-
30
if global1
is initialized before global2
-
10
if global2
is initialized before global1
, as global1
will only be zero-initialized to
0
.
This rule raises an issue if the initializer of a global variable a
, directly refers to another global variable b
, when
there is no guarantee that b
is initialized before a
.
This is a subset of the issues related to initialization of global variables, commonly referred to as a static initialization order fiasco.
What is the potential impact?
The behavior of any program that is prone to static initialization order fiasco issues, is very fragile. The order of initialization may change at
any time, for instance, when performing unrelated changes to the source code, or even differ between the builds of the same source. Such issues are
very hard to reproduce and debug.
Furthermore, the impact is not only limited to observing fluctuating values of global variables, as presented in the previous example.
For class types, invoking any member functions of an object that is only zero-initialized and for which the constructor has not run, may cause
undefined behavior and crash the program:
extern std::string const string1;
std::string const string2 = string1; // Noncompliant: undefined behavior
Even if, for a particular implementation, zero-initialized objects are equivalent to default-initialized objects, the program may still encounter
undefined behavior:
extern std::vector<std::string> const list;
// Defined in different source file as:
// std::vector<std::string> const list{"entry1", "entry2"};
std::string const firstElem = list[0]; // Noncompliant: undefined behavior
Static class data members also use dynamic initialization
Static data members of classes are dynamically initialized if their initializers are not constant expressions. Therefore, this rule will consider
static data members of classes as if they were global variables.
// Clazz.hpp
extern int global;
class Clazz {
static int staticMem;
};
// Clazz.cpp
#include "Clazz.hpp"
int Clazz::staticMem = global + 10; // Noncompliant: "global" may not yet be fully initialized
// Source.cpp
#include "Clazz.hpp"
int global = 10;
int otherGlobal = Clazz::staticMem; // Noncompliant: "Clazz::staticMem" may not yet be fully initialized
Initialization of instantiated variable is unordered
If a global variable instantiated from a template requires a dynamic initialization (because its initialization is non-constant), the ordering of
this initialization is unspecified. It may be performed before the initialization of a global variable declared before it. As a consequence, such
variables should not refer to any dynamically initialized global variable in their initializers, or be referred to from the initializer of any global
variable with dynamic initialization.
An instantiated variable may be produced directly from the instantiation of a variable template (introduced in C++14):
int global1 = runtimeFunc();
template<typename T>
T varTempl = global1; // Noncompliant: "global1" may be initialized later
int global2 = varTempl<long>; // Noncompliant: "varTempl" may be initialized later
Similarly, static data members of class template instantiation behave as instantiated global variables:
int global1 = runtimeFunc();
template<typename T>
struct Clazz {
static T staticMem;
};
template<typename T>
T Clazz<T>::staticMem = global1; // Noncompliant: "global1" may be initialized later
int global2 = Clazz<long>::staticMem; // Noncompliant: "Clazz<long>::staticMem" may be initialized later