Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: static on static member variable dependent initialization with int vs struct

Tags:

c++

static

c++11

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 */
like image 783
toting Avatar asked Oct 18 '16 12:10

toting


1 Answers

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 to StaticClass::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 ]

like image 146
eerorika Avatar answered Nov 15 '22 04:11

eerorika