寻找 C++ 静态初始化顺序问题
我们遇到了一些静态初始化顺序惨败,我正在寻找方法来梳理大量代码以找到可能发生的情况。 关于如何有效地做到这一点有什么建议吗?
编辑:我得到了一些关于如何解决静态初始化顺序问题的好答案,但这并不是我的问题。 我想知道如何查找存在此问题的对象。 埃文的答案似乎是迄今为止在这方面最好的答案; 我不认为我们可以使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
解决初始化的顺序:
首先,这只是一个临时的解决方法,因为你有一些全局变量,你试图摆脱它们,但还没有时间(你最终会摆脱它们不是吗? :-)
这将保证它在首次使用时初始化并在应用程序终止时销毁。
多线程问题:
C++11确实保证这是线程安全的:
然而,C++03 并没有正式保证静态函数对象的构造是线程安全的。 因此从技术上讲,
getInstance_XXX()
方法必须用临界区来保护。 好的一面是,gcc 有一个显式补丁作为编译器的一部分,保证每个静态函数对象即使在存在线程的情况下也只会被初始化一次。请注意:请勿使用双重检查锁定模式来尝试避免锁定的成本。 这在 C++03 中不起作用。
创建问题:
在创建时,不存在任何问题,因为我们保证它是在创建之后才可以使用的。
销毁问题:
在对象被销毁后访问该对象存在潜在的问题。 仅当您从另一个全局变量的析构函数访问该对象时才会发生这种情况(通过全局,我指的是任何非局部静态变量)。
解决方案是确保强制执行销毁顺序。
请记住,破坏的顺序与构造的顺序完全相反。 因此,如果您在析构函数中访问该对象,则必须保证该对象没有被销毁。 为此,您必须保证在构造调用对象之前完全构造对象。
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? :-)
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:
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.
我只是写了一些代码来解决这个问题。 我们有一个大小合适的代码库(1000 多个文件),在 Windows/VC++ 2005 上运行良好,但在 Solaris/gcc 上启动时崩溃。
我编写了以下 .h 文件:
在解决方案中的每个 .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:
and within every .cpp file in the solution, I added this:
When you run your application, you will get an output file like so:
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)
根据您的编译器,您可以在构造函数初始化代码处放置断点。 在 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.
也许使用 valgrind 来查找未初始化内存的使用情况。 解决“静态初始化顺序失败”的最好解决方案是使用一个静态函数,它返回一个对象实例,如下所示:
这种访问静态对象的方式是通过调用 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:
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.
有些代码本质上是“初始化”由编译器生成的 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.
这不是一个小问题,但如果您有一个易于解析的代码中间格式表示,那么至少可以通过相当简单的步骤来完成。
1)找到所有具有非平凡构造函数的全局变量并将它们放入列表中。
2) 对于每个非平凡构造的对象,生成由其构造函数调用的整个潜在函数树。
3)遍历非平凡构造函数树,如果代码引用任何其他非平凡构造的全局变量(它们在第一步中生成的列表中非常方便),则您有一个潜在的早期静态初始化顺序问题。
4) 重复步骤2& 3,直到您用完步骤 1 中生成的列表。
注意:如果您有单个类的多个全局变量,您可以通过每个对象类仅访问一次势函数树而不是每个全局实例一次来优化此功能。
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.
将所有全局对象替换为全局函数,该函数返回对函数中声明为静态的对象的引用。 这不是线程安全的,因此如果您的应用程序是多线程的,您可能需要一些技巧,例如 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.
您需要做的第一件事是列出所有具有重要构造函数的静态对象。
鉴于此,您要么需要一次插入它们,要么简单地将它们全部替换为单例模式对象。
单例模式受到了很多批评,但是懒惰的“按需”构造是解决现在和将来的大多数问题的相当简单的方法。
旧...
新...
当然,如果您的应用程序是多线程的,这可能会给您带来比一开始更多的问题...
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...
new...
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) 声称他们的 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.
其中一些答案现在已经过时了。 对于像我这样来自搜索引擎的人来说:
在 Linux 和其他地方,可以通过 Google 的 AddressSanitizer。
您将执行类似以下操作的 GCC 的一部分:
请参阅此处了解更多详细信息:
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.
You would then do something like the following:
See here for more details:
https://github.com/google/sanitizers/wiki/AddressSanitizerInitializationOrderFiasco
其他答案是正确的,我只是想补充一点,对象的 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....
如果您的项目位于 Visual Studio 中(我已使用 VC++ Express 2005 和 Visual Studio 2008 Pro 尝试过此操作):
这应该为您提供所有遭受失败的全局变量的不错的列表。
最后,更好的方法是尝试从项目中删除这些对象(有时说起来容易做起来难)。
If your project is in Visual Studio (I've tried this with VC++ Express 2005, and with Visual Studio 2008 Pro):
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).