8

I was fooling around with static class variables, and I ran into something unexpected that I don't understand.

Stroustrup's book TC++PL4 says in 15.4.1 Initialization of Nonlocal Variables, "There is no guaranteed order of initialization of global variables in different translation units." But see that's for multiple translation units; I wasn't testing dependencies between files, this was in a single file. The same section says "In principle, a variable defined outside any function (that is, global, namespace, and class static variables) is initialized before main() is invoked. Such nonlocal variables in a translation unit are initialized in their definition order.

If I set up this at the top of the file:

class Test1 {
public:
static double test1;
};

class Test2 {
public:
static double test2;
};

class Test3 {
public:
static double test3;
};

I can do this:

double Test2::test2 = Test1::test1;
double Test1::test1 = 1.125;

And put this statement in main to check the values:

cout << Test1::test1 << " " << Test2::test2;

And both values will be 1.125. I understand that both variables are in scope to be defined at that point, but if they are initialized in definition order how is it that a statement above is using the data from a statement below? Also, the order of class definitions at the top changes nothing in any of these cases.

If I use this arrangement:

double Test2::test2 = Test1::test1;
double Test3::test3 = Test2::test2;
double Test1::test1 = 1.125;

They will also all be 1.125.

But if I do this:

double Test3::test3 = Test2::test2;
double Test2::test2 = Test1::test1;
double Test1::test1 = 1.125;

Test3::test3 will be 0.

It seems like it can look for another definition one layer in, but if that definition needs yet another definition it stops and just assigns zero. But I don't know what's really happening.

Does anyone know why it does this? I tried this in the C++ '14 mode of http://cpp.sh (GCC) and in VSE 2012 with consistent results.

EDIT: See, I keep seeing everywhere that you can't predict it, but this observation seems to be glossing over the apparent fact that there's a difference between uncertainty across files and the (possibly nonexistent?) uncertainty within a single file. Stroustrup basically said there's a difference, but static variables are weird.

I had a thought that it's possible that a class definition looks outside the original file to initialize the static data member even in the case of a single file so that in terms of the compilation logic the file loops around to find itself again, generating UB. If a compiler were programmed to assign zero instead of jumping to a third external variable the result would be predictable and more stable, but I don't have any proof that a file can externally reference itself like this.

EDIT 2: It appears to be a matter of static and dynamic initialization, where static initialization assigns zero or a given constant at compile time and dynamic then uses those values (like Mark B said) as in this: What is dynamic initialization of object in c++? But I have to ask whether it's UB or not, still.

LAST EDIT: It wasn't hard to find information, the behavior is very well founded and not UB, see here: http://en.cppreference.com/w/cpp/language/initialization

Community
  • 1
  • 1
Lunarian
  • 185
  • 1
  • 6
  • 1
    just because the standard does not explicitly allow it, it does not mean that compilers cannot do it. Imho your results are completely consistent: If you stick to the rule everything is fine, if you dont sometimes you get `1.125` and sometimes `0` – 463035818_is_not_an_ai Nov 01 '15 at 22:05
  • @tobi303 are you saying this code contains undefined/unspecified /implementation-defined behaviour? – Bryan Chen Nov 01 '15 at 22:09
  • all static variables must be initialized before main call, but we cannot predict exact order of initialization (it is undifined). – Mykola Nov 01 '15 at 22:10
  • @BryanChen for some reason i was assuming that they have to be defined in the order of declaration, but now I think I am completely wrong – 463035818_is_not_an_ai Nov 01 '15 at 22:12
  • See, I keep seeing everywhere that you can't predict it, but this observation seems to be glossing over the apparent fact that there's a difference between uncertainty across files and the (possibly nonexistent?) uncertainty within a single file. Stroustrup basically said there's a difference, but static variables are weird. –  Lunarian Nov 01 '15 at 22:18
  • I had a thought that it's possible that a class definition looks outside the original file to initialize the static data member even in the case of a single file so that in terms of the compilation logic the file loops around to find itself again, generating UB. –  Lunarian Nov 01 '15 at 22:31
  • Added, I'm new to this –  Lunarian Nov 01 '15 at 22:35

1 Answers1

5

So what's happening here is that there are effectively two phases of the initialization: static and dynamic. The compiler can see that test1 is a constant and embeds that constant in the binary, default it to the value. It can't do so for test2 and test3, so those get values of 0 in the binary.

Then before main starts up, the dynamic initialization phase starts, puts the 0 value from test2 into test3 and then the 1.25 constant from test1 into test2.

All that said never rely on this and always initialize your variables in a sane order.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • So there is no UB there? – Bryan Chen Nov 01 '15 at 22:37
  • This makes perfect sense! And, with that lead I found a LOT more information. This link here explains how the behavior is in the C++ standard from 2003: http://stackoverflow.com/questions/5945897/what-is-dynamic-initialization-of-object-in-c –  Lunarian Nov 01 '15 at 22:42
  • But, I have to ask, given that the behavior seems so well-defined in the standard in terms of static and dynamic initialization with static being either 0 or a given constant, is this UB? It really doesn't actually look like UB. It's weird and impractical yes, but... –  Lunarian Nov 01 '15 at 22:43
  • Appears to not be UB on further examination, thank you for helping! –  Lunarian Nov 01 '15 at 22:54
  • [\[basic.start.init\]/3](http://eel.is/c++draft/basic.start.init#3) grants extra leeway to the implementation here. – T.C. Nov 02 '15 at 01:16