Finding C++ static initialization order problems

后端 未结 12 799
情话喂你
情话喂你 2020-11-22 10:10

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 s

相关标签:
12条回答
  • 2020-11-22 10:12

    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...

    0 讨论(0)
  • 2020-11-22 10:12

    Some of these answers are now out of date. For the sake of people coming from search engines, like myself:

    On Linux and elsewhere, finding instances of this problem is possible through Google's AddressSanitizer.

    AddressSanitizer is a part of LLVM starting with version 3.1 and a part of GCC starting with version 4.8

    You would then do something like the following:

    $ g++ -fsanitize=address -g staticA.C staticB.C staticC.C -o static 
    $ ASAN_OPTIONS=check_initialization_order=true:strict_init_order=true ./static 
    =================================================================
    ==32208==ERROR: AddressSanitizer: initialization-order-fiasco on address ... at ...
        #0 0x400f96 in firstClass::getValue() staticC.C:13
        #1 0x400de1 in secondClass::secondClass() staticB.C:7
        ...
    

    See here for more details: https://github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco

    0 讨论(0)
  • 2020-11-22 10:16

    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.

    0 讨论(0)
  • 2020-11-22 10:20

    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.

    0 讨论(0)
  • 2020-11-22 10:21

    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)

    0 讨论(0)
  • 2020-11-22 10:21

    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.

    0 讨论(0)
提交回复
热议问题