我们遇到了static initialization order fiasco的一些问题,我正在寻找方法来梳理一大堆代码来查找可能的事件。有关如何有效地做到这一点的任何建议?
编辑:关于如何解决静态初始化顺序问题,我得到了一些很好的答案,但这不是我的问题。我想知道如何查找受此问题影响的对象。在这方面,Evan的答案似乎是迄今为止最好的答案;我不认为我们可以使用valgrind,但我们可能有可以执行类似功能的内存分析工具。只有在给定构建的初始化顺序错误的情况下才能捕获问题,并且顺序可以随每个构建而改变。也许有一个静态分析工具可以捕捉到这一点。我们的平台是在AIX上运行的IBM XLC / C ++编译器。
答案 0 :(得分:64)
首先,这只是一个临时的解决办法,因为你有全局变量,你试图摆脱,但还没有时间(你最终会摆脱它们不是吗?: - )
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有一个显式的补丁作为编译器的一部分,它保证每个静态函数对象只有在线程存在的情况下才会初始化一次。
请注意:不要使用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.
}
};
答案 1 :(得分: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”宏放在尽可能靠近文件顶部的位置非常重要。如果你把它放在其他一些#includes之下,你就会在识别你所在的文件之前冒着崩溃的风险。
如果您正在使用Visual Studio和预编译的标头,则可以使用“查找和替换”快速将此额外宏行添加到.cpp文件的所有文件中使用相同文本和FIASCO_FINDER行替换现有#include“precompiledheader.h”的对话框(如果勾选“正则表达式,则可以使用”\ n“插入多行替换文本)
答案 2 :(得分:14)
根据编译器的不同,您可以在构造函数初始化代码中放置断点。在Visual C ++中,这是_initterm
函数,它给出了要调用的函数列表的开始和结束指针。
然后单步执行每个函数以获取文件和函数名称(假设您已使用调试信息编译)。获得名称后,请退出该功能(返回_initterm
)并继续,直到_initterm
退出。
这为您提供了 所有 静态初始值设定项,而不仅仅是代码中的静态初始值设定项 - 这是获取详尽列表的最简单方法。您可以过滤掉您无法控制的那些(例如第三方库中的那些)。
该理论适用于其他编译器,但函数名称和调试器的功能可能会发生变化。
答案 3 :(得分:5)
也许使用valgrind来查找未初始化内存的用法。 “静态初始化顺序惨败”最好的解决方案是使用一个静态函数,它返回一个对象的实例,如下所示:
class A {
public:
static X &getStatic() { static X my_static; return my_static; }
};
这样你通过调用getStatic访问你的静态对象,这将保证它在第一次使用时被初始化。
如果您需要担心去初始化的顺序,请返回一个new'd对象而不是静态分配的对象。
编辑:删除了冗余的静态对象,我不知道为什么但是我在原始示例中混合并匹配了两种静态方法。
答案 4 :(得分:5)
有一些代码基本上“初始化”由编译器生成的C ++。当时查找此代码/调用堆栈的一种简单方法是创建一个静态对象,其中包含在构造函数中取消引用NULL的内容 - 在调试器中中断并探索一下。 MSVC编译器设置一个函数指针表,迭代用于静态初始化。您应该能够访问此表并确定程序中发生的所有静态初始化。
答案 5 :(得分:4)
我们遇到了的一些问题 静态初始化命令fiasco, 我正在寻找梳理方法 通过大量代码查找 可能发生的事件。任何建议 如何有效地做到这一点?
这不是一个微不足道的问题,但如果你有一个易于解析的代码中间格式表示,至少它可以按照相当简单的步骤完成。
1)找到所有具有非平凡构造函数的全局变量并将它们放在一个列表中。
2)对于这些非平凡构造的对象中的每一个,生成由其构造函数调用的整个势函数树。
3)遍历非平凡构造函数树,如果代码引用任何其他非平凡构造的全局变量(在第一步中生成的列表中非常方便),则您有一个潜在的早期静态 - 初始化顺序问题。
4)重复步骤2&amp; 3,直到您用尽了步骤1中生成的列表。
注意:如果你有一个类的多个全局变量,你可以通过每个对象类访问一次potential-function-tree而不是每个全局实例一次来优化它。
答案 6 :(得分:2)
使用全局函数替换所有全局对象,这些函数返回对函数中声明为static的对象的引用。这不是线程安全的,因此如果您的应用程序是多线程的,您可能需要一些技巧,如pthread_once或全局锁定。这将确保在使用之前对所有内容进行初始化。
现在,要么你的程序工作(欢呼!),要么它处于无限循环中,因为你有一个循环依赖(需要重新设计),否则你继续下一个bug。
答案 7 :(得分:1)
您需要做的第一件事是列出所有具有非平凡构造函数的静态对象。
鉴于此,您需要一次插入一个,或者只是用单例模式对象替换它们。
单身模式引起了很多批评,但是懒惰的“按需”结构是解决现在和未来大部分问题的一种相当简单的方法。
...老
MyObject myObject
...新
MyObject &myObject()
{
static MyObject myActualObject;
return myActualObject;
}
当然,如果您的应用程序是多线程的,这可能会导致您遇到的问题比您最初的问题更多......
答案 8 :(得分:1)
Gimpel Software(www.gimpel.com)声称他们的PC-Lint / FlexeLint静态分析工具将检测到这些问题。
我对他们的工具有很好的经验,但没有这个特定的问题,所以我不能保证他们会有多大帮助。
答案 9 :(得分:1)
其中一些答案现在已过时。为了像我这样的人来自搜索引擎:
在Linux和其他地方,可以通过Google的AddressSanitizer查找此问题的实例。
AddressSanitizer是LLVM的一部分,从3.1版开始,并且 从4.8版开始的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
答案 10 :(得分:0)
如果你的项目在Visual Studio中(我用VC ++ Express 2005和Visual Studio 2008 Pro尝试过这个):
这应该为你提供一个体面的列表,列出所有受 the fiasco 影响的全局变量。
最后,更好的方法是尝试从项目中删除这些对象(有时说起来容易做起来难。)
答案 11 :(得分:0)
其他答案都是正确的,我只是想补充说,对象的getter应该在.cpp文件中实现,它不应该是静态的。如果您在头文件中实现它,那么将在您从....调用它的每个库/框架中创建该对象。