program tip

C ++ 정적 초기화 순서 문제 찾기

radiobox 2020. 12. 11. 08:02
반응형

C ++ 정적 초기화 순서 문제 찾기


우리는 정적 초기화 순서 실패 와 관련하여 몇 가지 문제에 부딪 혔 으며 가능한 발생을 찾기 위해 많은 코드를 살펴 보는 방법을 찾고 있습니다. 이를 효율적으로 수행하는 방법에 대한 제안이 있습니까?

편집 : 정적 초기화 순서 문제를 해결하는 방법에 대한 좋은 답변을 얻었지만 실제로는 제 질문이 아닙니다. 이 문제의 대상이되는 개체를 찾는 방법을 알고 싶습니다. Evan의 대답은 이와 관련하여 지금까지 가장 좋은 것 같습니다. valgrind를 사용할 수 없다고 생각하지만 비슷한 기능을 수행 할 수있는 메모리 분석 도구가있을 수 있습니다. 이는 주어진 빌드에 대해 초기화 순서가 잘못된 경우에만 문제를 포착하고 각 빌드마다 순서가 바뀔 수 있습니다. 아마도 이것을 잡을 수있는 정적 분석 도구가있을 것입니다. 우리의 플랫폼은 AIX에서 실행되는 IBM XLC / C ++ 컴파일러입니다.


초기화 순서 해결 :

먼저, 제거하려고하지만 아직 시간이 없었던 전역 변수가 있기 때문에 일시적인 해결 방법 일뿐입니다 (결국 제거 할 것입니까? :-)

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

이렇게하면 처음 사용할 때 초기화되고 응용 프로그램이 종료 될 때 폐기됩니다.

다중 스레드 문제 :

C ++ 11 이것이 스레드로부터 안전함을 보장합니다.

§6.7 [stmt.dcl] p4
변수가 초기화되는 동안 제어가 선언에 동시에 들어 오면 동시 실행은 초기화가 완료 될 때까지 기다려야합니다.

그러나 C ++ 03은 정적 함수 개체의 구성이 스레드로부터 안전하다는 것을 공식적으로 보장 하지 않습니다 . 따라서 기술적으로 getInstance_XXX()방법은 중요한 섹션으로 보호되어야합니다. 긍정적 인면에서 gcc는 컴파일러의 일부로 명시 적 패치를 가지고있어 각 정적 함수 객체가 스레드가있는 경우에도 한 번만 초기화되도록 보장합니다.

참고 : 잠금 비용을 피하기 위해 이중 체크 잠금 패턴사용 하지 마십시오 . 이것은 C ++ 03에서 작동하지 않습니다.

창조 문제 :

제작시에는 사용하기 전에 만들어 졌음을 보증하므로 문제가 없습니다.

파괴 문제 :

오브젝트가 파괴 된 후 액세스하는 데 잠재적 인 문제가 있습니다. 이것은 다른 전역 변수의 소멸자에서 객체에 액세스하는 경우에만 발생합니다 (전역 적으로 비 로컬 정적 변수를 참조합니다).

해결책은 파괴 명령을 강요하는 것입니다.
파괴 순서는 건설 순서의 정반대임을 기억하십시오. 따라서 소멸자의 객체에 액세스하는 경우 객체가 파괴되지 않았는지 확인해야합니다. 이렇게하려면 호출 개체가 생성되기 전에 개체가 완전히 생성되었는지 확인해야합니다.

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

이 문제를 추적하기 위해 약간의 코드를 작성했습니다. Windows / VC ++ 2005에서는 제대로 작동하지만 Solaris / gcc에서 시작시 충돌하는 적절한 크기의 코드 기반 (1000 개 이상의 파일)이 있습니다. 다음 .h 파일을 작성했습니다.

#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)


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.


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.


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.


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.


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.


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


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.


Other answers are correct, I just wanted to add that the object's getter should be implemented in a .cpp file and it should not be static. If you implement it in a header file, the object will be created in each library / framework you call it from....


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

참고URL : https://stackoverflow.com/questions/335369/finding-c-static-initialization-order-problems

반응형