Given a static member variable which is initialized from a static member variable of another class, the non-literal struct ii
is sometimes default initialized to 0
or to 333
. This depends on the compilation or linking order. Pseudocode to demonstrate:
class StaticClass: // file 'ONE.cpp/.hpp'
static int i = 2
static struct ii { int a = 333 }
class ABC: // file 'abc.cpp'
static int abc_i = StaticClass::i // always 2
static struct abc_ii = StaticClass::ii // sometimes 0, sometimes 333
Calling g++ -std=c++11 abc.cpp ONE.cpp && ./a.out
results in i = 2 / ii = 0
(gcc 4.8.1, same with clang++ 3.7; -Wall -Wextra
never complain).
But calling g++ -std=c++11 ONE.cpp abc.cpp && ./a.out
results in i = 2 / ii = 333
!
The same happens with ONE.o abc.o
vs abc.o ONE.o
and also when concatenating the files one way or another:
cat ONE.cpp abc.cpp > X.cpp && g++ X.cpp && ./a.out
vs
cat abc.cpp ONE.cpp > Y.cpp && g++ Y.cpp && ./a.out
Removing includes and moving code around in the single file, the default initialization to 0 happens when this order is present:
const OneI ABC::def_ii = StaticClass::ii; const OneI StaticClass::ii = OneI{333};
and the one to 333 with this ordering:
const OneI StaticClass::ii = OneI{333}; const OneI ABC::def_ii = StaticClass::ii;
Why does this even happen with two separate compilation units? Can this be avoided somehow by enforcing the latter ordering all the time? Is using a static pointer in ABC
to StaticClass::ii
safe (I'd prefer not to, though)?
Full C++ code:
/* File: abc.cpp */
#include <iostream>
#include "ONE.hpp"
struct ABC {
ABC();
static const int def_i;
static const OneI def_ii;
void arg_i(const int &x) { std::cout << "i = " << x << " ";};
void arg_ii(const OneI &x) { std::cout << "/ ii = " << x.a << " ";};
};
ABC::ABC() {
arg_i(def_i);
arg_ii(def_ii);
}
const int ABC::def_i = StaticClass::i;
const OneI ABC::def_ii = StaticClass::ii;
int main() {
ABC a;
std::cout << '\n';
}
/* End: abc.cpp */
/* File: ONE.cpp */
#include <iostream>
#include "ONE.hpp"
const int StaticClass::i = 2;
const OneI StaticClass::ii = OneI{333};
/* End: ONE.cpp */
/* File: ONE.hpp */
#include <iostream>
#ifndef One
#define One
struct OneI {
OneI(int a_) : a(a_) { }
int a;
};
struct StaticClass {
const static int i;
const static OneI ii;
};
#endif // One header guard
/* End: ONE.hpp */
Congratulations! You've encountered the static initialization order fiasco.
The initialization order of static objects is not defined across multiple translation units.
StaticClass::ii
is defined in ONE.cpp
and ABC::def_ii
is defined in abc.cpp
. Therefore StaticClass::ii
may or may not be initialized before ABC::def_ii
. Since the initialization of ABC::def_ii
uses the value of StaticClass::ii
the value will depend on whether StaticClass::ii
was initialized yet†.
The initialization order of static objects within a translation unit is defined. Objects are initialized in the order in which they are defined. Therefore when you concatenate the source files, the order of initialization is defined. However, when you concatenate the files in the wrong order, the defined initialization order is wrong:
const OneI ABC::def_ii = StaticClass::ii; // StaticClass::ii wasn't initialized yet
const OneI StaticClass::ii = OneI{333};
Can this be avoided somehow by enforcing the latter ordering all the time?
Most trivial solution is to define both objects in the same translation unit, in the correct order. A more general solution is to initialize your static objects using Construct On First Use Idiom.
Is using a static pointer in
ABC
toStaticClass::ii
safe (I'd prefer not to, though)?
As long as the dereferenced value of the pointer isn't used during the initialization of a static object in another translation unit that where the pointed object is defined, yes, replacing ABC::def_ii
with a pointer would be safe.
†StaticClass::ii
will have been zero-initialized during the static initialization phase††. The Static initialization order fiasco concerns the dynamic initialization phase††.
†† C++ standard draft [basic.start.static]
If constant initialization is not performed, a variable with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) is zero-initialized ([dcl.init]). 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. [ Note: The dynamic initialization of non-local variables is described in [basic.start.dynamic]; that of local static variables is described in [stmt.dcl]. — end note ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With