代码之家  ›  专栏  ›  技术社区  ›  Fred Larson

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

  •  55
  • Fred Larson  · 技术社区  · 17 年前

    我们遇到了一些问题 static initialization order fiasco ,我正在寻找方法来梳理大量代码,以找到可能出现的情况。有没有关于如何有效地做到这一点的建议?

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

    11 回复  |  直到 5 年前
        1
  •  74
  •   Community Mohan Dere    5 年前

    初始化的求解顺序:

    首先,这只是一个暂时的解决办法,因为您有一些全局变量,您正试图消除它们,但还没有时间(您最终将消除它们,不是吗?:-)

    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
    如果控件在初始化变量时并发输入声明,则并发执行应等待初始化完成。

    正式保证静态函数对象的构造是线程安全的。所以从技术上来说 getInstance_XXX()

    请注意: 不要 使用 double checked locking pattern 尝试并避免锁定的成本。这在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.
            }
    };
    
        2
  •  31
  •   Antonio    8 年前

    我只是写了一些代码来追踪这个问题。我们有一个很好的代码库(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文件中。至少,这将为您提供一个设置断点的好位置,因为这段代码应该是 绝对优先 要执行的代码的一部分(在此之后,您可以逐步浏览代码并查看所有正在初始化的全局变量)。

    笔记:

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

        3
  •  15
  •   paxdiablo    10 年前

    根据编译器的不同,可以在构造函数初始化代码处放置断点。在VisualC++中,这是 _initterm 函数,该函数是要调用的函数列表的开始和结束指针。

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

    全部的 静态初始值设定项,而不仅仅是代码中的初始值设定项——这是获得详尽列表的最简单方法。您可以过滤掉无法控制的内容(例如第三方库中的内容)。

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

        4
  •  5
  •   Evan Teran    17 年前

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

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

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

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

        5
  •  5
  •   Ben Murrell    15 年前

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

        6
  •  4
  •   Adisak    16 年前

    我们遇到了一些问题 静态初始化命令失败, 我在寻找梳理的方法 如何有效地做到这一点?

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

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

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

    3) 遍历非平凡构造函数树,如果代码引用任何其他非平凡构造的全局函数(在步骤1中生成的列表中非常方便),则可能存在早期静态初始化顺序问题。

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

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

        7
  •  1
  •   Steve Jessop    17 年前

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

    现在,要么你的程序工作(万岁!)或者它位于一个无限循环中,因为您有一个循环依赖项(需要重新设计),或者您转到下一个bug。

        8
  •  1
  •   Roddy    17 年前

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

    有鉴于此,您要么需要一次插入一个,要么简单地用单例模式对象替换它们。

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

    古老的

    MyObject myObject
    

    新的。。。

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

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

        9
  •  1
  •   jww avp    10 年前

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

        10
  •  1
  •   Jack Yates    6 年前

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

    在Linux和其他地方,通过谷歌的 AddressSanitizer .

    GCC的一部分,从版本4.8开始

    然后,您将执行以下操作:

    $ 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

        11
  •  0
  •   Ryan    14 年前

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

        12
  •  0
  •   sjngm quinti    14 年前

    如果您的项目在Visual Studio中(我已经用VC++Express 2005和Visual Studio 2008 Pro尝试过这一点):

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

    这应该会给你一个很好的列表,列出所有受影响的全局变量 .

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