C++ 查找C++静态初始化顺序问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/335369/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:44:47  来源:igfitidea点击:

Finding C++ static initialization order problems

c++initializationstatic-order-fiasco

提问by Fred Larson

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?

我们在静态初始化顺序 fiasco方面遇到了一些问题,我正在寻找方法来梳理大量代码以查找可能出现的情况。关于如何有效地做到这一点的任何建议?

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.

编辑:关于如何解决静态初始化顺序问题,我得到了一些很好的答案,但这并不是我真正的问题。我想知道如何查找受此问题影响的对象。到目前为止,埃文的回答似乎是最好的。我不认为我们可以使用 valgrind,但我们可能有可以执行类似功能的内存分析工具。这只会在给定构建的初始化顺序错误的情况下捕获问题,并且顺序可以随着每个构建而改变。也许有一个静态分析工具可以解决这个问题。我们的平台是运行在 AIX 上的 IBM XLC/C++ 编译器。

回答by Martin York

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 doesguarantee that this is thread-safe:

C++11确实保证这是线程安全的:

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

§6.7 [stmt.dcl] p4
如果在初始化变量的同时控制进入声明,并发执行将等待初始化完成。

However, C++03 does notofficially 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.

但是,C++03 并没有正式保证静态函数对象的构造是线程安全的。因此,从技术上讲,该getInstance_XXX()方法必须用临界区来保护。从好的方面来说,gcc 有一个显式补丁作为编译器的一部分,它保证每个静态函数对象即使在存在线程的情况下也只会被初始化一次。

Please note: Do notuse the double checked locking patternto try and avoid the cost of the locking. This will not work in C++03.

请注意:不要使用双重检查锁定模式来尝试避免锁定成本。这在 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.
        }
};

回答by Warren Stevens

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:

我只是写了一些代码来追踪这个问题。我们有一个很好的代码库(1000 多个文件),在 Windows/VC++ 2005 上运行良好,但在 Solaris/gcc 上启动时崩溃。我写了以下 .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:

并在每个溶液中的.cpp文件,我加了这一点:

#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 firstof your code to execute (after which you can step through your code and see all of the globals that are being initialized).

如果您遇到崩溃,罪魁祸首应该在列出的最后一个 .cpp 文件中。至少,这将为您提供一个设置断点的好地方,因为此代码应该是要执行的代码的绝对第一个(之后您可以单步执行代码并查看所有正在初始化的全局变量) .

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

  • 将“FIASCO_FINDER”宏尽可能靠近文件顶部非常重要。如果你把它放在其他一些 #includes 之下,你就有可能在识别你所在的文件之前它崩溃。

  • 如果你正在使用Visual Studio,以及预编译头,加入这个额外的宏行所有的cpp文件可以快速使用进行查找和替换对话框,以取代现有的#include“precompiledheader.h”相同的文本加上 FIASCO_FINDER 行(如果您选中“正则表达式,您可以使用“\n”插入多行替换文本)

回答by paxdiablo

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

根据您的编译器,您可以在构造函数初始化代码处放置一个断点。在 Visual C++ 中,这是一个_initterm函数,它被赋予一个要调用的函数列表的开始和结束指针。

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

然后进入每个函数以获取文件和函数名称(假设您已使用调试信息进行编译)。获得名称后,退出函数(返回到_initterm)并继续直到_initterm退出。

That gives you allthe 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.

该理论适用于其他编译器,但函数名称和调试器的功能可能会改变。

回答by Ben Murrell

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.

有一些代码实质上是“初始化”由编译器生成的 C++。当时找到此代码/调用堆栈的一种简单方法是创建一个静态对象,其中包含在构造函数中取消引用 NULL 的内容 - 中断调试器并探索一下。MSVC 编译器设置了一个函数指针表,该表被迭代用于静态初始化。您应该能够访问此表并确定程序中发生的所有静态初始化。

回答by Evan Teran

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:

也许使用 valgrind 来查找未初始化内存的使用情况。“静态初始化顺序失败”的最佳解决方案是使用静态函数,该函数返回对象的实例,如下所示:

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.

这种访问静态对象的方式是通过调用 getStatic,这将保证它在第一次使用时被初始化。

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.

编辑:删除了多余的静态对象,我不知道为什么,但我在原始示例中混合并匹配了两种将静态放在一起的方法。

回答by Adisak

We've run into some problems with thestatic initialization order fiasco,and I'm looking for ways to combthrough a whole lot of code to findpossible occurrences. Any suggestionson 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.

1) 找到所有具有非平凡构造函数的全局变量并将它们放入列表中。

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

2) 对于这些非平凡构造的对象中的每一个,生成由它们的构造函数调用的整个势函数树。

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.

3) 遍历非平凡构造函数树,如果代码引用了任何其他非平凡构造的全局变量(它们在您在第一步中生成的列表中非常方便),则您有一个潜在的早期静态初始化顺序问题。

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

4) 重复步骤 2 和 3,直到用完步骤 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.

注意:如果您有单个类的多个全局变量,您可以通过每个对象类只访问一次潜在函数树而不是每个全局实例一次来优化它。

回答by Martin Vuille

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

Gimpel Software (www.gimpel.com) 声称他们的 PC-Lint/FlexeLint 静态分析工具将检测到此类问题。

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.

我对他们的工具有很好的经验,但在这个特定问题上没有经验,所以我不能保证他们会提供多少帮助。

回答by Steve Jessop

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.

用返回对函数中声明为静态的对象的引用的全局函数替换所有全局对象。这不是线程安全的,因此如果您的应用程序是多线程的,您可能需要一些技巧,例如 pthread_once 或全局锁。这将确保所有内容在使用之前都已初始化。

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.

现在,要么你的程序工作(万岁!),要么因为你有循环依赖(需要重新设计)而陷入无限循环,否则你会继续下一个错误。

回答by Roddy

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

当然,如果您的应用程序是多线程的,这可能会给您带来比最初更多的问题......

回答by Hyman Yates

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.

在 Linux 和其他地方,可以通过 Google 的AddressSanitizer找到此问题的实例。

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

AddressSanitizer 从 3.1 版开始是 LLVM 的一部分,从 4.8 版开始是 GCC 的一部分

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

有关更多详细信息,请参见此处:https: //github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco