views:

4743

answers:

9

We've run into some problems with the static initialization order fiasco, and I'm looking for ways to comb through a whole lot of code to find possible occurrences. Any suggestions on how to do this efficiently?

Edit: I'm getting some good answers on how to SOLVE the static initialization order problem, but that's not really my question. I'd like to know how to FIND objects that are subject to this problem. Evan's answer seems to be the best so far in this regard; I don't think we can use valgrind, but we may have memory analysis tools that could perform a similar function. That would catch problems only where the initialization order is wrong for a given build, and the order can change with each build. Perhaps there's a static analysis tool that would catch this. Our platform is IBM XLC/C++ compiler running on AIX.

+2  A: 

perhaps use valgrind to find usage of uninitialized memory. The nicest solution to the "static initialization order fiasco" is to use a static function which returns an instance of the object like this:

class A {
public:
    static X &getStatic() { static X my_static; return my_static; }
};

This way you access your static object is by calling getStatic, this will guarantee it is initialized on first use.

If you need to worry about order of de-initialization, return a new'd object instead of a statically allocated object.

EDIT: removed the redundant static object, i dunno why but i mixed and matched two methods of having a static together in my original example.

Evan Teran
this answer is fine. and de-initialization is also guarenteed: it's the reverse of the completion of the constructors, also across translation units. so all is fine then i think. it won't leak even
Johannes Schaub - litb
@litb, the C++ FAQ Lite seems to disagree with you on destruction order. See http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12. You know the standard well -- is there something in there that proves Marshall Cline wrong? 8v)
Fred Larson
I think the issue with using the static object ref is you can end up with references to dead object during program termination.
Evan Teran
@Evan, I think something is not right in your example. The getStatic() method returns a value, but it seems to have nothing to do with the something_static member. Does something_static have a purpose?
Fred Larson
This way you access A::something_static by calling getStatic << yeah i don't get that too :/. either you go with A::something_static, or you go with calling getStatic(); and use what it returns i think
Johannes Schaub - litb
OOPs! i was thinking of 2 different things when I typed that, fixed now.
Evan Teran
arg. please forget about my comment above. i messed up. "if x in its ctor calls y::get().foo(); then y's ctor will finish before x' ctor. this means that y will be destructed before x, since y's ctor finished before x ones." < indeed i mean the other way: y will be destructed *after* x.
Johannes Schaub - litb
i removed my comments to not confuse the matter even more. see martin's fine answer.
Johannes Schaub - litb
A: 

Replace all the global objects with global functions that return a reference to an object declared static in the function. This isn't thread-safe, so if your app is multi-threaded you might need some tricks like pthread_once or a global lock. This will ensure that everything is initialized before it is used.

Now, either your program works (hurrah!) or else it sits in an infinite loop because you have a circular dependency (redesign needed), or else you move on to the next bug.

Steve Jessop
A: 

The first thing you need to do is make a list of all static objects that have non-trivial constructors.

Given that, you either need to plug through them one at a time, or simply replace them all with singleton-pattern objects.

The singleton pattern comes in for a lot of criticism, but the lazy "as-required" construction is a fairly easy way to fix the majority of the problems now and in the future.

old...

MyObject myObject

new...

MyObject &myObject()
{
  static MyObject myActualObject;
  return myActualObject;
}

Of course, if your application is multi-threaded, this can cause you more problems than you had in the first place...

Roddy
It's not the singelton pattern. It has a similar implementation but it is not the same thing.
Martin York
Scott Meyers told me it was ;-) [effective C++, 2nd ed, P 222]
Roddy
+1  A: 

Depending on your compiler, you can place a breakpoint at the constructor initialization code. In Visual C++, this is the _initterm function which is given a start and end pointer which is a list of the function to call.

Then step into each function to get the file and function name (assuming you're compiled with debugging info on). Once you have the names, step out of the function (back up to _initterm) and continue until _initterm exits.

That gives you ALL he static initializers, not just the ones in your code - it's the easiest way to get an exhaustive list. You can filter out the ones you have no control over (such as those in third-party libraries).

The theory holds for other compilers but the name of the function and the capability of the debugger may change.

paxdiablo
+10  A: 

Solving order of initialization:

First off this is just a work around becuase you have global variables, that you are trying to get rid of but just have not had time yet (you are going to get rid of them eventually arn't you :-)

class A
{
    public:
        // Get the globale instance abc
        //
        static A& getInstance_abc()  // return reference.
        {
            static A instance_abc;
            return instance_abc;
        }
};

This will gurantee that it is initialised on first use and destroyed when the application terminates.

Multi Threaded Problem.

The language does not offically gurantee that the construction of ststaic function objects is thread safe (note the language gurantees nothing about threads). So technically the getInstance_XXX() method must be guarded with a critical section. On the bright side gcc has an explicit patch as part of the compiler that gurantees that each static function object will only be initialized once even in the presence of threads.

Please note:
DO NOT use the double checked locking pattern to try an optimize away the locking. This will not work in C++

Creation Problems:

On creation there are no problems because we garantee that it is created before it can be used.

Destruction Problems:

There is a potential problem of accessing the object after it has been destroyed. This only happens if you access the object from the destructor of another global variable (by global I am refering to any non local static variable).

Solution you must make sure you force the order of destruction.
Remember the order of destruction is the exact inverse of the order of construction. So if you access the object in your destructor you must gurantee that the object has not been destroyed. To do this you must just gurantee that the object is fully constructed before the calling object is constructed.

class B
{
    public:
        static B& getInstance_Bglob;
        {
            static B instance_Bglob;
            return instance_Bglob;;
        }

        ~B()
        {
             A::getInstance_abc().doSomthing();
             // The object abc is accessed from the destructor.
             // Potential problem.
             // You must guarantee that abc is destroyed after this object.
             // To gurantee this you must make sure it is constructed first.
             // To do this just access the object from the constructor.
        }

        B()
        {
            A::getInstance_abc();
            // abc is now fully constructed.
            // This means it was constructed before this object.
            // This means it will be destroyed after this object.
            // This means it is safe to use from the destructor.
        }
};
Martin York
I don't get to get rid of them. I didn't create them. I'm just stuck with the task of finding them. As far as I know, they are all const objects, which isn't quite so bad.
Fred Larson
Double-checked locking works just fine in C++. It is not *guaranteed* to work, but in practice it does. There is a difference between the two.
coryan
@coryan, @Martin York: Not only sequence points, but things like CPU instruction reordering, speculative execution, and cache line invalidation across multiple CPUs. Use a thread API, its the only way to be sure.
Zan Lynx
Matthieu M.
@coryan: you forgot to add "..until it breaks".
peterchen
+3  A: 

We've run into some problems with the static initialization order fiasco, and I'm looking for ways to comb through a whole lot of code to find possible occurrences. Any suggestions on how to do this efficiently?

It's not a trivial problem but at least it can done following fairly simple steps if you have an easy-to-parse intermediate-format representation of your code.

1) Find all the globals that have non-trivial constructors and put them in a list.

2) For each of these non-trivially-constructed objects, generate the entire potential-function-tree called by their constructors.

3) Walk through the non-trivially-constructor function tree and if the code references any other non-trivially constructed globals (which are quite handily in the list you generated in step one), you have a potential early-static-initialization-order issue.

4) Repeat steps 2 & 3 until you have exhausted the list generated in step 1.

Note: you may be able to optimize this by only visiting the potential-function-tree once per object class rather than once per global instance if you have multiple globals of a single class.

Adisak
A: 

Gimpel Software (www.gimpel.com) claims that their PC-Lint/FlexeLint static analysis tools will detect such problems.

I have had good experience with their tools, but not with this specific issue so I can't vouch for how much they would help.

MV

Martin Vuille
A: 

If your project is in Visual Studio (I've tried this with VC++ Express 2005, and with Visual Studio 2008 Pro):

1) Open Class View (Main menu->View->Class View) 2) Expand each project in your solution and Click on "Global Functions and Variables"

This should give you a decent list of all of the globals that are subject to the fiasco.

In the end, a better approach is to try to remove these objects from your project (easier said than done, sometimes).

Warren Stevens
Thanks, but you'll see in my question that our platform is IBM XL C/C++ under AIX. Visual Studio isn't an option. I agree it'd be best to get rid of them; the challenge is finding them all.
Fred Larson
A: 

I just wrote a bit of code to track down this problem. We have a good size code base (1000+ files) that was working fine on Windows/VC++ 2005, but crashing on startup on Solaris/gcc. I wrote the following .h file:

#ifndef FIASCO_H
#define FIASCO_H

/////////////////////////////////////////////////////////////////////////////////////////////////////
// [WS 2010-07-30] Detect the infamous "Static initialization order fiasco"
// email warrenstevens --> [initials]@[firstnamelastname].com 
// read --> http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 if you haven't suffered
// To enable this feature --> define E-N-A-B-L-E-_-F-I-A-S-C-O-_-F-I-N-D-E-R, rebuild, and run
//#define ENABLE_FIASCO_FINDER
/////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef ENABLE_FIASCO_FINDER

#include <iostream>
#include <fstream>

inline bool WriteFiasco(const std::string& fileName)
{
    static int counter = 0;
    ++counter;

    std::ofstream file;
    file.open("FiascoFinder.txt", std::ios::out | std::ios::app);
    file << "Starting to initialize file - number: [" << counter << "] filename: [" << fileName.c_str() << "]" << std::endl;
    file.flush();
    file.close();
    return true;
}

// [WS 2010-07-30] If you get a name collision on the following line, your usage is likely incorrect
#define FIASCO_FINDER static const bool g_psuedoUniqueName = WriteFiasco(__FILE__);

#else // ENABLE_FIASCO_FINDER
// do nothing
#define FIASCO_FINDER

#endif // ENABLE_FIASCO_FINDER

#endif //FIASCO_H

and within every .cpp file in the solution, I added this:

#include "PreCompiledHeader.h" // (which #include's the above file)
FIASCO_FINDER
#include "RegularIncludeOne.h"
#include "RegularIncludeTwo.h"

When you run your application, you will get an output file like so:

Starting to initialize file - number: [1] filename: [p:\\OneFile.cpp]
Starting to initialize file - number: [2] filename: [p:\\SecondFile.cpp]
Starting to initialize file - number: [3] filename: [p:\\ThirdFile.cpp]

If you experience a crash, the culprit should be in the last .cpp file listed. And at the very least, this will give you a good place to set breakpoints, as this code should be the absolute first of your code to execute (after which you can step through your code and see all of the globals that are being initialized).

Notes:

  • It's important that you put the "FIASCO_FINDER" macro as close to the top of your file as possible. If you put it below some other #includes you run the risk of it crashing before identifying the file that you're in.

  • If you're using Visual Studio, and pre-compiled headers, adding this extra macro line to all of your .cpp files can be done quickly using the Find-and-replace dialog to replace your existing #include "precompiledheader.h" with the same text plus the FIASCO_FINDER line (if you check off "regular expressions, you can use "\n" to insert multi-line replacement text)

Warren Stevens