1
votes

I read about the static initialization order fiasco in C++ relating to crash of the application. I think I understood it but still have few questions:
1) If I want to reproduce this problem, how can I do it (so that my program should crash)? I would like to write a test program to reproduce a crash. Can you please provide the source code if possible?
2) I read this C++ FAQ Lite article and it says that it has two static objects, x and y, in two different files and y calls x's method. How its possible as global static members have file level scope?
3) This problem is very dangerous, is there any attempt to fix it on compiler level?
4) How many times you C++ experts faced this problem in real life production?

3
It's static as in static storage, not static C variable. This includes: a) any global variable (whether exported, declared static or in anonymous namespace), b) any static data member of a class (in some implementations including virtual function table and rtti information), c) static local variable (statics declared inside functions; their order of initialization is determined by order of calls to functions, so in single threaded program they don't usually cause problem with order of initialization).Tomek Szpakowicz

3 Answers

3
votes

EDIT: adjusted to make it more accurate in light of comments.

A good example would look like this:

// A.cpp
#include "A.h"
std::map<int, int> my_map;

// A.h
#include <map>
extern std::map<int, int> my_map;

// B.cpp
#include "A.h"
class T {
public:
    T() { my_map.insert(std::make_pair(0, 0)); }
};

T t;

int main() {
}

The problem is that the instance t may be constructed before the my_map object. So the insert may occur on a yet-to-be constructed object. Causing a crash.

A simple solution is to do something like this instead:

// A.h
#include <map>
std::map<int, int> &my_map()

// A.cpp
#include "A.h"
std::map<int, int> &my_map() {
    // initialized on first use
    static std::map<int, int> x;
    return x;
}

// B.cpp
#include "A.h"

class T {
public:
    T() { my_map().insert(std::make_pair(0, 0)); }
};

T t;

int main() {
}

By accessing a static object via a function, we can guarantee order of initialization since function scope statics are initialized upon first use. Thus the t object is constructed first, it calls my_map() which creates a static map object on its first run and then returns a reference to it.

2
votes

1) You'd either have to examine the runtime startup code to see how it selects the order of initialization, or experiment a bit. You could improve the odds of having the error by creating an initialization dependency between more than two objects, perhaps 3 or 4.

2) Just instantiate an object at file level:

OBJECT_TYPE x;

3) No compiler addresses this that I know of. It would require detection at or after linking.

4) In practice, it's easy to avoid: make all initializations self-contained.

1
votes

"1) If I want to reproduce this problem, how can I do it (so that my program should crash)? I would like to write a test program to reproduce a crash. Can you please provide the source code if possible?"

You can't write a portable test case. The static initialization order fiasco is that order is not defined. The problem occurs when someone writes code that will work if they're initialized in one legal order, but will fail if they're initialized in some other legal order. So for the same reason that you can't guarantee it will work, you can't guarantee it will fail. That's the whole point.

You could perhaps take a guess that the linker will initialize all the globals from one translation unit before all the globals from another. So set up two source files A and B, with globals A1 and A2 in A, and B1 and B2 in B. Then use B1 (by which I mean, "do something which fails if B1 has not been initialized") in the constructor of A1, and use A2 in the constructor of B2. Also use A1 in the constructor of A2 (and declare them in that order in A). Then the only order which won't fail is B1, A1, A2, B2, which you might imagine is a pretty unlikely choice for the implementation to make. On a particular implementation, if it does somehow succeed, switch things so that A2 uses B2 instead of B2 using A2, and just hope that doesn't change the initialization order.

Of course you could also use B2 in the constructor of B1 (and declare them in that order in B), to guarantee failure no matter what the initialization order. But then that wouldn't be the static initialization order fiasco, it would just be a fundamentally broken circular dependency.

"2) I read this C++ FAQ Lite article and it says that it has two static objects, x and y, in two different files and y calls x's method. How its possible as global static members have file level scope?"

For example declare them extern in both translation units (perhaps using a common header). Scope, linkage and storage duration are all different things.

"3) This problem is very dangerous, is there any attempt to fix it on compiler level?"

Not that I know of. I'm pretty sure it's a halting problem to work out whether or not object X "uses" (in the sense I defined above) object Y in its constructor, so constructing a dependency graph at link time and t-sorting it would be at best a partial measure.

"4) How many times you C++ experts faced this problem in real life production?"

Never, because (a) I don't leave globals lying around, and (b) where I have used them, I have avoided doing anything fancy in their initializers. Basically, don't design a class and then decide to have a global instance of it - if you're going to use a global object, design it as a global. And wherever possible, use locally-scoped statics rather than global statics. If you need to offer something that looks like a global, publish it as a function which returns a reference to the object, or as an object which they can create on their stack, and which calls that function for them and then acts as a proxy (or handle, if you like) for the global state. You still have to worry about thread safety, but threaded environments provide ways to manage that, whereas there is no way to manage the fiasco other than by getting your callers to define your globals in their translation unit.

It only gets difficult if you're implementing an API which defines globals, like std::out. There's a trick you can use, where you define a dummy file-scope variable in the same header which declares the global. I can't remember the name, though.