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

发布于 2024-07-09 20:38:29 字数 416 浏览 11 评论 0原文

我们遇到了一些静态初始化顺序惨败,我正在寻找方法来梳理大量代码以找到可能发生的情况。 关于如何有效地做到这一点有什么建议吗?

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

望她远 2024-07-16 20:38:30

解决初始化的顺序:

首先,这只是一个临时的解决方法,因为你有一些全局变量,你试图摆脱它们,但还没有时间(你最终会摆脱它们不是吗? :-)

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

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.
        }
};
回心转意 2024-07-16 20:38:30

我只是写了一些代码来解决这个问题。 我们有一个大小合适的代码库(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

在解决方案中的每个 .cpp 文件中,我添加了以下内容:

#include "PreCompiledHeader.h" // (which #include's the above file)
FIASCO_FINDER
#include "RegularIncludeOne.h"
#include "RegularIncludeTwo.h"

当您运行应用程序时,您将获得如下输出文件:

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]

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

注意:

  • 将“FIASCO_FINDER”宏尽可能靠近文件顶部放置非常重要。 如果您将其放在其他一些 #include 之下,则在识别您所在的文件之前,您将面临崩溃的风险。

  • 如果您使用的是 Visual Studio 和预编译标头,请将此额外的宏行添加到 <强>所有您的.cpp文件都可以使用“查找和替换”对话框快速完成,用相同的文本加上FIASCO_FINDER行替换现有的#include“precompiledheader.h”(如果您选中“常规”表达式,您可以使用“\n”插入多行替换文本)

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)

半葬歌 2024-07-16 20:38:30

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

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

这为您提供了所有静态初始值设定项,而不仅仅是代码中的静态初始值设定项 - 这是获取详尽列表的最简单方法。 您可以过滤掉您无法控制的内容(例如第三方库中的内容)。

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

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.

小ぇ时光︴ 2024-07-16 20:38:30

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

class A {
public:
    static X &getStatic() { static X my_static; return my_static; }
};

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

如果您需要担心取消初始化的顺序,请返回一个新的对象而不是静态分配的对象。

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

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.

旧梦荧光笔 2024-07-16 20:38:30

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

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.

纸伞微斜 2024-07-16 20:38:30

我们遇到了一些问题
静态初始化顺序惨败,
我正在寻找梳理的方法
通过一大堆代码才找到
可能发生的情况。 任何建议
如何有效地做到这一点?

这不是一个小问题,但如果您有一个易于解析的代码中间格式表示,那么至少可以通过相当简单的步骤来完成。

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

2) 对于每个非平凡构造的对象,生成由其构造函数调用的整个潜在函数树。

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

4) 重复步骤2& 3,直到您用完步骤 1 中生成的列表。

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

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.

会发光的星星闪亮亮i 2024-07-16 20:38:30

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

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

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.

花辞树 2024-07-16 20:38:30

您需要做的第一件事是列出所有具有重要构造函数的静态对象。

鉴于此,您要么需要一次插入它们,要么简单地将它们全部替换为单例模式对象。

单例模式受到了很多批评,但是懒惰的“按需”构造是解决现在和将来的大多数问题的相当简单的方法。

旧...

MyObject myObject

新...

MyObject &myObject()
{
  static MyObject myActualObject;
  return myActualObject;
}

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

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

半衬遮猫 2024-07-16 20:38:30

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

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

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.

因为看清所以看轻 2024-07-16 20:38:30

其中一些答案现在已经过时了。 对于像我这样来自搜索引擎的人来说:

在 Linux 和其他地方,可以通过 Google 的 AddressSanitizer

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

您将执行类似以下操作的 GCC 的一部分:

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

请参阅此处了解更多详细信息:
https://github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco

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

橙幽之幻 2024-07-16 20:38:30

其他答案是正确的,我只是想补充一点,对象的 getter 应该在 .cpp 文件中实现,并且它不应该是静态的。 如果您在头文件中实现它,则该对象将在您调用它的每个库/框架中创建......

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

一袭白衣梦中忆 2024-07-16 20:38:30

如果您的项目位于 Visual Studio 中(我已使用 VC++ Express 2005 和 Visual Studio 2008 Pro 尝试过此操作):

  1. 打开类视图(主菜单 -> 视图 -> 类视图)
  2. 展开解决方案中的每个项目,然后单击关于“全局函数和变量”

这应该为您提供所有遭受失败的全局变量的不错的列表。

最后,更好的方法是尝试从项目中删除这些对象(有时说起来容易做起来难)。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文