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.
C++ guarantees that variables in a compilation unit (. cpp file) are initialised in order of declaration. For number of compilation units this rule works for each one separately (I mean static variables outside of classes). But, the order of initialization of variables, is undefined across different compilation units.
The static initialization order fiasco refers to the ambiguity in the order that objects with static storage duration in different translation units are initialized in.
In C, static variables can only be initialized using constant literals. For example, following program fails in compilation. If we change the program to following, then it works without any error.
Problem of initialization in C++ Therefore, when objects are created, the members of the object cannot be initialized directly and this problem of not being able to initialize data members is known as the problem 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.
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.
On creation, there are no problems because we guarantee that it is created before it can be used.
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. } };
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With