Finding C++ static initialization order problems

58,428

Solution 1

Solving order of initialization:

First off, this is just a temporary work-around because 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 aren't you? :-)

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

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

Multi-Threaded Problem:

C++11 does guarantee that this is thread-safe:

§6.7 [stmt.dcl] p4
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

However, C++03 does not officially guarantee that the construction of static function objects is thread safe. 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 guarantees 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 and avoid the cost of the locking. This will not work in C++03.

Creation Problems:

On creation, there are no problems because we guarantee 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 referring to any non-local static variable).

The solution is to make sure that 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 guarantee that the object has not been destroyed. To do this, you must just guarantee 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 guarantee 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.
        }
};

Solution 2

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)

Solution 3

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 of a list of the functions to call.

Then step into each function to get the file and function name (assuming you've 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 the 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.

Solution 4

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.

Solution 5

There is code that essentially "initializes" C++ that is generated by the compiler. An easy way to find this code / the call stack at the time is to create a static object with something that dereferences NULL in the constructor - break in the debugger and explore a bit. The MSVC compiler sets up a table of function pointers that is iterated over for static initialization. You should be able to access this table and determine all static initialization taking place in your program.

Share:
58,428
Drew Delano
Author by

Drew Delano

Updated on July 05, 2022

Comments

  • Drew Delano
    Drew Delano almost 2 years

    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.

  • Johannes Schaub - litb
    Johannes Schaub - litb over 15 years
    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
  • Drew Delano
    Drew Delano over 15 years
    @litb, the C++ FAQ Lite seems to disagree with you on destruction order. See 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)
  • Jason
    Jason over 15 years
    I think the issue with using the static object ref is you can end up with references to dead object during program termination.
  • Drew Delano
    Drew Delano over 15 years
    @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?
  • Johannes Schaub - litb
    Johannes Schaub - litb over 15 years
    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
  • Jason
    Jason over 15 years
    OOPs! i was thinking of 2 different things when I typed that, fixed now.
  • Johannes Schaub - litb
    Johannes Schaub - litb over 15 years
    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
    Johannes Schaub - litb over 15 years
    i removed my comments to not confuse the matter even more. see martin's fine answer.
  • Martin York
    Martin York over 15 years
    It's not the singelton pattern. It has a similar implementation but it is not the same thing.
  • Drew Delano
    Drew Delano over 15 years
    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.
  • coryan
    coryan over 15 years
    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.
  • Zan Lynx
    Zan Lynx over 15 years
    @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.
  • Roddy
    Roddy over 15 years
    Scott Meyers told me it was ;-) [effective C++, 2nd ed, P 222]
  • Matthieu M.
    Matthieu M. about 14 years
    Neat trick to enforce the lifetime. Note that if we went DI we would have: B(A&) which would neatly solve the issue. Of course not being default constructable has its own woes so perhaps B(): mInstanceOfA(A::getInstance()) {}
  • Drew Delano
    Drew Delano almost 14 years
    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.
  • peterchen
    peterchen almost 14 years
    @coryan: you forgot to add "..until it breaks".
  • Freshzak187
    Freshzak187 about 13 years
    I call the above 'construct on first use' pattern. Just to clarify, singleton pattern ensures a class has only one instance and provides a global point of access to it. 'Construct on first use' is/can be the part that provides 'a global point of access' part of the impementation of a singleton. However, on its own, doesn't ensure a single instance.
  • Ryan Pavlik
    Ryan Pavlik almost 13 years
    Doesn't the one-definition-rule prevent this from happening?
  • Chad
    Chad over 11 years
    This seems helpful for small projects. Unfortunately for me I have a codebase of 1000s of implementation files, and hundreds of thousands of lines of code :/
  • Warren Stevens
    Warren Stevens over 10 years
    Chad - There is often a way to make a quick replacement, even on 1000+ files (e.g. if you already have precompiled headers, you can search/replace on that header name, or write a small script to do the replacement on all the files). I did the former on our project and it had many 100s of files.
  • paulm
    paulm over 9 years
    How is this fixing the problem? Does this give a defined init/deinit ordering?
  • Roddy
    Roddy over 9 years
    @paulm - 'construct on first use' above means that your object can't be used before its constructed. (typical problem would be two global objects in separate compilation units). And they're destructed in the reverse order, so yes, the order is defined.
  • jww
    jww over 8 years
    This just moves the problem to the destructors. Been there, done that, got the tee-short and the crash when one object was destroyed too soon at program exit.
  • Martin York
    Martin York over 8 years
    @jww: You should read then. Because we guarantee the order of construction and destruction so there is no problem. That is the point.
  • jww
    jww over 8 years
    We must have different meanings of the word "problem" then. I'd call a crash a problem....
  • Martin York
    Martin York over 8 years
    @jww: Then you must be doing it wrong. Rered until you understand how C++ works :-) OK sorry being an ass. Its easy to do it wrong. But I have found that this problem is simple to fix because of the guarantees of the language. Of course you can still get it binds. But the simplest solution is obviously to not use globals (or static storage duration variables).
  • jww
    jww over 8 years
    @Loki - thanks. I believe I have a pretty good understanding of it. Perhaps you can update your answer to address the order of destruction across translation units?
  • Martin York
    Martin York over 8 years
    @jww. It already handles that. Which was the point of writing this answer. It enforces an order of construction and destruction across all static storage duration objects. Though it requires you to do a tiny bit of work. This is simply a workaround for bad programming practice where you have globals but it guarantees that all objects are created and destroyed in a well defined order. Which means the interactions are well defined.
  • jww
    jww over 8 years
    Forgive my ignorance... How, exactly, do you do this across translation units?: "Solution you must make sure you force the order of destruction..."
  • Martin York
    Martin York over 8 years
    Read the comments inside the code above. The order of destruction is the exact inverse of the order of destruction (guaranteed by the language). So all you need to do is create them in the correct order. You can do that by using the well known pattern to access the object in the constructor.
  • Antonio
    Antonio about 7 years
    @Chad With a good grep tool, like grepWin, one could do this with regular expressions. Replace [\s\S]* (entire file) with #include "HeaderContainingTheMacro.h"\nFIASCO_FINDER\n$0 for all your *.cpp/cxx/cc files. The $0 is to put back the entire file after having placed your preamble.
  • Deduplicator
    Deduplicator almost 7 years
    "C++03 does not officially guarantee that the construction of static function objects is thread safe". Well, it doesn't acknowledge the existence of multithreading at all.
  • Martin York
    Martin York almost 7 years
    @Deduplicator: And hence it does not provide any guarantee that construction is thread safe.
  • Deduplicator
    Deduplicator almost 7 years
    Yes, I know that, but the way you say it, it looks like you are saying "C++03 knows threads, but for some no doubt asinine reason they didn't make that construct thread-safe" instead of "C++03 does not know threads, so you are on your own. Use ..."
  • kylefinn
    kylefinn over 6 years
    @Loki Worth noting this order of destruction does not apply to thread locals which are accessing a normal static var.
  • Boris Dalstein
    Boris Dalstein over 6 years
    Nit-picking: regarding destruction, the concern with this solution is that all clients of A (such as B::instance) must call A::getInstance() in their constructor if they wish to use A::getInstance() in their destructor. I agree that in most situations, this occurs naturally, but there are cases where it might not. In fact, I believe this is why logging mechanisms (such as std::cout) are typically implemented using the Nifty Counter idiom: this allows a class B to perform logging only on destruction, which would be unsafe if the logger was implemented as in this answer.
  • Boris Dalstein
    Boris Dalstein over 6 years
    See also isocpp.org/wiki/faq/ctors#construct-on-first-use-v2 : However if a and/or b and/or c fail to use ans in their constructors and/or if any code anywhere gets the address of ans and hands it to some other static object, all bets are off and you have to be very, very careful.
  • mada
    mada over 2 years
    "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" Please bro, Can you provide an example of how this problem can happen?
  • Martin York
    Martin York over 2 years
    @AccessDenied I do above. With the solution. In the class B above remove the call to A::getInstance_abc(); from the constructor and you now have a "potential" issue.
  • mada
    mada over 2 years
    @MartinYork, Oh thanks, I got it: the problem will happen if we call a member function getInstance().doSomthing() in the destructor of this object, and at this moment getInstance() hasn't been constructed yet. So the Fix: construct getInstance() first in the constructor of this object.
  • mada
    mada over 2 years
    Just answer me if possible; if I am having static object 'a' and 'b' constructed respectively, now if 'a' destructed before 'b', regardless of how this can happen. Does the program is ill-formed.