0

Doesn't a compiler have all the information it needs to generate a dependency tree of all globals and create a well defined and correct initialization order for them? I realize you could write a cyclic dependency with globals - make only that case undefined behavior - and the compiler could warn and maybe error about it.

Usually the reason for this sort of thing is that it would be burdensome to compiler makers or cause compilation to slow significantly. I have no metrics or evidence that indicates either of these wouldn't be true in this case, but my inclination is that neither would be true.

David
  • 25,830
  • 16
  • 80
  • 130
  • Well, "unspecified" doesn't preclude a compiler from specifying the order... I guess nobody has done it yet? – Kerrek SB Sep 20 '13 at 19:44
  • 1
    What about a global defined in one translation unit whose initialization depends on a global in another translation unit being initialized? You'd be imposing some sort of link-time optimization on all compilers. – Praetorian Sep 20 '13 at 19:46
  • @Praetorian What kind of crap compiler _doesn't_ already do 'some sort of link-time optimization'? – David Sep 20 '13 at 19:48
  • The problem is that the linker will need to know/understand what static initialization needs to be done in which order. That could turn out to be very complex, and the linker is generally not very clever... – Mats Petersson Sep 20 '13 at 19:48
  • @MatsPetersson I'm far from an expert on this but my understanding is that modern linkers do a whole lot of code optimization. If you, or anyone, is knowledgeable on how complex that is, or how 'clever' the linker is, please provide details - I'm very curious if this would _actually_ entail something difficult beyond the scope of what linkers already do. – David Sep 20 '13 at 19:52
  • 1
    @Dave It's probably likely the mainstream compilers/linkers are smart enough to resolve initialization dependencies as you suggest; however, the standard itself needs to cater to the lowest common denominator. I'm sure there are dozens of compilers for embedded targets that have no idea how to look across object files to perform the most basic LTO. – Praetorian Sep 20 '13 at 19:58
  • @Dave: In the worst case this is not that different from the halting problem. The linker would have to see how each global is initailized, which might be through a function call. The linker knows what are the dependencies in that function, but it would need to verify whether with the arguments provided in this particular case, the code in the function access that other global or not, as that might affect the ordering... – David Rodríguez - dribeas Sep 20 '13 at 20:14
  • I would imagine the most practical reason no ordering has been defined is that it's easier to tell people not to try to not rely on it, and that works well for almost everyone. – GManNickG Sep 20 '13 at 20:15
  • The assumption that the compiler has a view of all globals is false. The compiler sees one compilation unit at a time. The final linking is typically made at run time when shared libraries are loaded, and at runtime the content of libraries might differ from the content at compile time. – smossen Sep 20 '13 at 20:44

3 Answers3

5

Hm, imagine the following setup, which is perfectly valid C++, but tricky to analyze:

// TU #1

bool c = coin();

// TU #2

extern bool c;
extern int b;
int a = c ? b : 10;

// TU #3

extern bool c;
extern int a;
int b = c ? 20 : a;

It is clear that TU #1 needs to be initialized first, but then what? The standard solution with references-to-statics allows you to write this code correctly with standard C++, but solving this by fixing the global initialization order seems tricky.

Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • 1
    By "seems tricky" I probably mean that the compiler would need to be able to generate arbitrary meta code, and possibly solve the halting problem. – Kerrek SB Sep 20 '13 at 20:03
  • Lol... that's a funny case. Stupid ternary. The initialization order would have to change depending on the value of `c`, which might not be compile time resolved (correct?). So, the only way to solve that is to allow the initialization order to change in run time depending on something in _run time_? Ya I doubt that'll ever happen. – David Sep 20 '13 at 20:10
  • 1
    @Dave: Well, it's hardly the fault of the conditional operator. You could have written any kind of equivalently troublesome code. This was just the shortest example I could come up with. – Kerrek SB Sep 20 '13 at 20:15
3

The part the compiler can deal with is actually define: objects with static storage duration are constructed in the order their definition appears in the translation unit. The destruction order is just the reverse.

When it comes to ordering objects between translation units, the dependency group for objects is typically not explicitly represented. However, even if the dependencies were explicitly represnted, they wouldn't actually help much: on small projects the dependencies between objects with static storage duration can be managed relatively easy. Where things become interesting are large objects but these have a much higher chance to include initializations of the form

static T global = functionWhichMayuseTheword();

i.e., in the case where the ordering would be useful it is bound not to work.

There is a trivial way to make sure objects are constructed in time which is even thread-safe in C++ (it wasn't thread-safe in C++03 as this standard didn't mention any concept of threads in the first place): Use a function local static object and return a reference to it. The objects will be constructed upon demand but if there are dependencies between them this is generally acceptable:

static T& global() {
    static rc = someInitialization();
    return rc;
}

Given that there is a simple work-around and neither a proposal nor a working implementation demonstrating that the proposal does work, there is little interest to change the state of how global objects are initialized. Not to mention that improving the support for global objects seems as useful as making goto better.

Dietmar Kühl
  • 141,209
  • 12
  • 196
  • 356
2

I am not a compiler author so take what I say with a grain of salt. I think the reasons are as follows.

1) Desire the preserve the C model of separate compilation. Link time analysis is certainly allowed, but I suspect they did not want to make it required.

2) Meyers Singleton (especially now that it has been made thread-safe) provides a good enough alternative in that it is almost as easy to use as a global variable but provides the guarantees you are looking for.

John Bandela
  • 2,296
  • 10
  • 18