C++ static initialization order
You have answered your own question. Static initialization order is undefined, and the most elegant way around it (while still doing static initialization i.e. not refactoring it away completely) is to wrap the initialization in a function.
Read the C++ FAQ items starting from https://isocpp.org/wiki/faq/ctors#static-init-order
Can the static initialization order fiasco occur in C programs?
Static initialization in C does not have the same problems that C++ has.
In C, objects with static storage duration may only be initialized via constant expressions, i.e. values that can be computed at compile time, so there are no issues that can arise regarding order of initialization.
In contrast, C++ allows calling functions to initialize static objects, and the order in which those functions are called are not well-defined.
Prevent static initialization order fiasco, C++
The modern, more pattern-oriented way is not to use globals in the first place.
There's no other way around it.
It wouldn't be much of a "fiasco", otherwise!
static global variables initialization order
6.6.3 Dynamic initialization of non-local variables [basic.start.dynamic]
- Dynamic initialization of a non-local
variable with static storage duration is unordered if the variable is
an implicitly or explicitly instantiated specialization, is
partially-ordered if the variable is an inline variable that is not an
implicitly or explicitly instantiated specialization, and otherwise is
ordered. [ Note: An explicitly specialized non-inline static data
member or variable template specialization has ordered initialization.
— end note ]- Dynamic initialization of non-local variables V and W
with static storage duration are ordered as follows:
- If V and
W have ordered initialization and V is defined before W within a
single translation unit, the initialization of V is sequenced before
the initialization of W.- If V
has partially-ordered initialization, W does not have unordered
initialization, and V is defined before W in every translation unit in
which W is defined, then
- if the program starts a thread
(4.7) other than the main thread (6.6.1), the initialization of V
strongly happens before the initialization of W;- otherwise,
the initialization of V is sequenced before the initialization of W.- Otherwise, if the program starts a thread other than the main
thread before either V or W is initialized, it is unspecified in which
threads the initializations of V and W occur; the initializations are
unsequenced if they occur in the same thread.- Otherwise, the
initializations of V and W are indeterminately sequenced.
Quoted from N4659, formatting adjusted to work with the markdown supported here.
For the exact definition of dynamic initialization, see the preceding subsesction 6.6.2 [basic.start.static].
Static inline members initialization order
Yes, this is fine, since in every translation unit static_counter
is defined before static_a
/static_b
. Destruction order is not guaranteed to be the reverse (given threads, this is meaningless anyway), but the reverse of each guarantee holds, so that works too.
C++ static variables initialization order
The first scenario is well-defined in [basic.start.init]/2:
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.
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 (5.19) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 12.2);
- if an object with static or thread storage duration is initialized by a constructor call, if the constructor is a
constexpr
constructor, if all constructor arguments are constant expressions (including conversions), and if, after function invocation substitution (7.1.5), every constructor call and full-expression in the mem-initializers and in the brace-or-equal-initializers for non-static data members is a constant expression;- if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.
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. (...)
(Emphasis mine)
The upshot of this fairly lengthy paragraph is that
int n = 2;
is static initialization, while
int k = n;
is dynamic initialization (because n
is not a constant expression), and therefore n
is initialized before k
even if it appears later in the code.
The same logic applies in the case of the Base::static_constructor
example -- because the constructor of Base::static_constructor
is not constexpr
, Base::constructor
is dynamically initialized, whereas Base::i
is statically initialized. The initialization of Base::i
therefore takes place before the initialization of Base::constructor
.
On the other hand, the second case with
int n = func();
puts you squarely in the territory of unspecified behavior, and it is quite explicitly mentioned in [basic.start.init]/3:
An implementation is permitted to perform the initialization of a non-local variable with static storage duration as a static initialization even if such initialization is not required to be done statically, provided that
- the dynamic version of the initialization does not change the value of any other object of namespace scope prior to its initialization, and
- the static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically.
[Note: As a consequence, if the initialization of an object
obj1
refers to an objectobj2
of namespace scope potentially requiring dynamic initialization and defined later in the same translation unit, it is unspecified whether the value ofobj2
used will be the value of the fully initializedobj2
(becauseobj2
was statically initialized) or will be the value ofobj2
merely zero-initialized. For example,inline double fd() { return 1.0; }
extern double d1;
double d2 = d1; // unspecified:
// may be statically initialized to 0.0 or
// dynamically initialized to 0.0 if d1 is
// dynamically initialized, or 1.0 otherwise
double d1 = fd(); // may be initialized statically or dynamically to 1.0
-- end note]
Related Topics
C++0X Has No Semaphores? How to Synchronize Threads
Difference Between Static and Dynamic Arrays in C++
Finding C++ Static Initialization Order Problems
How to Properly Delete Nodes of Linked List in C++
Is a String Literal in С++ Created in Static Memory
Does "Int Size = 10;" Yield a Constant Expression
Do You Use Null or 0 (Zero) For Pointers in C++
Is It Better to Use Std::Memcpy() or Std::Copy() in Terms to Performance
Installing Opencv 2.4.3 in Visual C++ 2010 Express
Position of Least Significant Bit That Is Set
Why Do C and C++ Compilers Allow Array Lengths in Function Signatures When They'Re Never Enforced
How to Use Queryperformancecounter
Difference of Keywords 'Typename' and 'Class' in Templates
How to Pass a Multidimensional Array to a Function in C and C++
Returning Unique_Ptr from Functions
What's the Difference Between Assignment Operator and Copy Constructor