static initialization order fiasco

Pages: 12
Jun 12, 2011 at 3:13pm
are there any tools that can detect this type of bug?

what do you personally do to avoid these types of problems?

edit: change the title of the thread to include order
Last edited on Jun 12, 2011 at 7:55pm
Jun 12, 2011 at 4:09pm
the static keyword has a lot of definitions... What's up?
Jun 12, 2011 at 4:19pm
Not sure what fiasco you refer to. Please elaborate.
Jun 12, 2011 at 4:26pm
He means this:

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14

And I don't know of any tools to detect it. But basically the general rule of thumb is that if something depends on an external object being constructed, don't make it global.
Jun 12, 2011 at 4:42pm
1) Make the global object that is referred to a static member of a static function, accessible only via function call.
2) Have global objects only available via pointers, and instantiate them in main() in the correct order.
3) Design to avoid the problem.

Just for information, I have been programming C++ for 10 years, and I would say the last 3 have been at a quite advanced level. I have never once encountered this problem.
Jun 12, 2011 at 5:00pm
I think it can easily be avoided by writing "static constructors" (static functions in the classes that need some type of initialization), and then calling the static constructors yourself from a class that initializes a bool by calling a function: LibInit() or similar:

1
2
3
4
5
6
7
8
9
10
11
12
class _NotImportant
{
    static bool LibInit();
    static const bool _isInitialized;
};

bool _NotImportant::LibInit()
{
    //Do what you need to do in the order you need to do it.
}

const bool _NotImportant::_isInitialized = LibInit();


I think that effectively simulates static constructors running before main() and in the desired order.
Jun 12, 2011 at 5:35pm
ok - let me be more specific - given the following chunk of code which statics have the potential of causing a fiasco?

correct me if I'm wrong, but I think static1 and static2 can potentially cause problems while static3 and static4 seem to be ok... also, if I'm missing any other static initialization cases, please comment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyClass
{
  public:
    MyClass( int x ) : m_x( x ) { }  // implementation may change over time
    void foo();

    static MyClass static2;

  private:
    int m_x;  // more data members may be added over time
};

static MyClass   static1( 1 );  // safe or may have problems with changes over time?
MyClass MyClass::static2( 2 );  // safe or may have problems with changes over time?

void
MyClass::foo()
{
  static MyClass static3( 3 );  // safe or may have problems with changes over time?
}

static void bar()
{
  static MyClass static4( 4 );  // safe or may have problems with changes over time?
}

int main()
{
}


edit: it seems to me that the fiasco is caused by indeterminate initialization ordering at the module level (.cpp) - since static variables inside of methods and functions cannot be seen outside at the module level, they seem to be ok (they strictly will be initialized the first time the method/function is called)

edit: added some more comments to code to indicate what may change over time
Last edited on Jun 12, 2011 at 7:39pm
Jun 12, 2011 at 7:08pm
None of those should cause any problems because they don't refer to each other, so the order of initialization is irrelevant.
Jun 12, 2011 at 7:21pm
static2 could be referred to by some other module, since it is public (I was lazy here and didn't break out .hpp and .cpp files or talk about other modules, for simplicity)

static1, in its current implementation, doesn't refer to another module, but if someone modified the implementation of its constructor, it could cause problems...

(if the above is not evident, I can post examples)

as far as I can tell, static3 and static4 do not have these issues

static initialization order fiasco is insidious because the compiler does not (usually) find these sorts of problems - if you are not careful, your program can run fine for years and then one day, as you modify some unrelated code, it suddenly breaks during run-time

edit: although I like Disch's rule, I am trying to determine if we follow the rule "just don't leave any initializations outside of any functions or methods", we can avoid the static initialization order fiasco - I am looking for a simple rule of thumb because as projects get bigger and bigger, it becomes easier to accidentally step into problems like this fiasco, in a non-obvious manner - especially if you are playing with other people's code or they are playing with yours
Last edited on Jun 12, 2011 at 7:40pm
Jun 12, 2011 at 7:47pm
@webJose, suppose I have in another module

1
2
3
4
5
6
7
8
9
10
11
12
class _NotImportant2
{
    static bool LibInit();
    static const bool _isInitialized;
};

bool _NotImportant2::LibInit()
{
    //Do what you need to do in the order you need to do it.
}

const bool _NotImportant2::_isInitialized = _NotImportant2::LibInit();


1. it is indeterminate which one will be initialized first (_NotImportant or _NotImportant2's LibInit() call)

2. it currently doesn't matter which one is initialized first, but suppose over time, as both classes get bigger and more complex, one day, the initialization of one depends on the other (sometimes, in a non-obvious manner); when that happens, you will get a fiasco unless you change the code

edit: even worse, when 2. happens, your code may run fine/undetected with no apparent problems whatsoever for a while; this is a problem not detected during compile-time: it depends on both your run path and an arbitrary initialization order which may change from build to build
Last edited on Jun 12, 2011 at 7:54pm
Jun 12, 2011 at 8:04pm
I wonder if Java's static initialization blocks were created to avoid C++'s SIOF - from what I can tell from reading up on them, having this feature in Java seems to prevent SIOF from happening in Java...
Last edited on Jun 12, 2011 at 8:30pm
Jun 12, 2011 at 8:12pm
Without knowing what kind of changes could conceivably happen, all of these can potentially break.
If MyClass::MyClass(int) references a global object, the states of static1 and static2 are undefined.
If an object of a class that references static2 is declared global, its state becomes undefined.
If MyClass::foo() or MyClass::bar() each end up in different threads at the same time, during their first run, the state of static3 or static4 become undefined.

You can solve some of these problems with lazy initialization. That eliminates some of the uncertainty.
Jun 12, 2011 at 8:20pm
Well, ok. Both static 1 and 2 can be referenced by other modules, neither static 3 or 4 can. You'll note this was in fact point 1 in my earlier post.

I still don't see it as a major concern that anyone really needs to worry about - just code in a way where it can't happen, eg make sure the only global objects you create are weak_ptr<>s Then there is no problem.

Is there a reasonable situation where there is absolutely no choice but to create a global object? Off the top of my head I can't think of one.
Jun 12, 2011 at 8:25pm
excellent point on static3 and static4 for being not thread-safe!

since initialization of static3 and static4 depends on runtime ordering, we are in trouble without mutexes here (just imagine two threads that call foo() without any calls to foo() previous to spawning the threads - this is a race condition without a mutex lock)
Jun 12, 2011 at 8:33pm

If MyClass::foo() or MyClass::bar() each end up in different threads at the same time, during their first run, the state of static3 or static4 become undefined.


Why would they end up in different threads? Functions with static variables are not thread safe, so they shoudn't be called?
Jun 12, 2011 at 8:37pm
kev82 wrote:
Is there a reasonable situation where there is absolutely no choice but to create a global object? Off the top of my head I can't think of one.

nope, there isn't

usually, globals are used for some kind optimization purposes (like Singletons are usually wanted for optimization purposes) so that you don't keep initializing the same resources or keep reading from the same file or device or database that doesn't change during your run
Jun 12, 2011 at 8:45pm
kev82 wrote:
Why would they end up in different threads? Functions with static variables are not thread safe, so they shoudn't be called?

functions with static variables shouldn't be called by more than one thread without some kind of mutex protection (essentially, any shared mutable resource including any memory which is changeable by a thread needs to be protected with a mutex to avoid a race condition)

there are certain exceptions like if the global memory is initialized and immutable before any threads are spawned - then you are ok without a mutex
Last edited on Jun 12, 2011 at 10:07pm
Jun 12, 2011 at 9:07pm
Functions with static variables are not thread safe, so they shoudn't be called?
functions with static variables shouldn't be called by more than one thread without some kind of mutex protection
Yeah, sure. And global objects shouldn't reference other global objects in their constructors.
Just because the rule is there doesn't mean you'll remember to follow it each time, which is what this thread is about.
Jun 13, 2011 at 6:11am
Ok, so I'm back. I brought a quick test with me. See http://ideone.com/GVQYX for full code.

I admit I did not expect to have to add lines 18, 19, 40 and 41. Adding those put me in trouble, I would say, right? The test code runs OK and yields the expected result (variables altered by the code in the Init() static function), but I guess it is probably a matter of a coincidence. I suppose one cannot really tell in a one-file project, correct?

So this is interesting. I guess I never thought about this before. Does the runtime initialize globals in the same way, meaning the fiasco can also be present in globals?
Jun 13, 2011 at 6:17am
Based on my sample code: Wouldn't you guys dare say that every compiler will behave correctly? Because let's check the Init() function: It uses the class' assignment operator (as it is not allowed to construct those) to set the correct value that I, the programmer, want for those static members.

Because the Init() function uses the assignment operator, isn't the compiler forced to initialize the variable BEFORE Init() so that operator= can be guaranteed an appropriate behavior?

The downside I see: I cannot apply this approach to static const variables.
Pages: 12