C++ 静态变量初始化顺序
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/211237/
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
Static variables initialisation order
提问by Dmitry Khalatov
C++ guarantees that variables in a compilation unit (.cpp file) are initialised in order of declaration. For number of compilation units this rule works for each one separately (I mean static variables outside of classes).
C++ 保证编译单元(.cpp 文件)中的变量按照声明的顺序进行初始化。对于编译单元的数量,此规则分别适用于每个单元(我的意思是类之外的静态变量)。
But, the order of initialization of variables, is undefined across different compilation units.
但是,变量的初始化顺序在不同的编译单元中是不确定的。
Where can I see some explanations about this order for gcc and MSVC (I know that relying on that is a very bad idea - it is just to understand the problems that we may have with legacy code when moving to new GCC major and different OS)?
我在哪里可以看到关于 gcc 和 MSVC 的这个顺序的一些解释(我知道依赖它是一个非常糟糕的主意 - 这只是为了了解我们在迁移到新的 GCC 主要和不同的操作系统时可能遇到的遗留代码的问题) ?
回答by Martin York
As you say the order is undefined across different compilation units.
正如您所说,不同编译单元的顺序是未定义的。
Within the same compilation unit the order is well defined: The same order as definition.
在同一个编译单元中,顺序是明确定义的:与定义相同的顺序。
This is because this is not resolved at the language level but at the linker level. So you really need to check out the linker documentation. Though I really doubt this will help in any useful way.
这是因为这不是在语言级别解决,而是在链接器级别解决。所以你真的需要查看链接器文档。虽然我真的怀疑这是否会以任何有用的方式有所帮助。
For gcc: Check out ld
对于 gcc:查看ld
I have found that even changing the order of objects files being linked can change the initialization order. So it is not just your linker that you need to worry about, but how the linker is invoked by your build system. Even try to solve the problem is practically a non starter.
我发现即使改变被链接的对象文件的顺序也会改变初始化顺序。因此,您不仅需要担心链接器,还需要担心构建系统如何调用链接器。即使尝试解决问题实际上也无济于事。
This is generally only a problem when initializing globals that reference each other during their own initialization (so only affects objects with constructors).
这通常只是在初始化在其自身初始化期间相互引用的全局变量时才会出现问题(因此仅影响具有构造函数的对象)。
There are techniques to get around the problem.
有一些技术可以解决这个问题。
- Lazy initialization.
- Schwarz Counter
- Put all complex global variables inside the same compilation unit.
- 延迟初始化。
- 施瓦茨计数器
- 将所有复杂的全局变量放在同一个编译单元中。
- Note 1: globals:
Used loosely to refer to static storage duration variables that are potentially initialized beforemain()
. - Note 2: Potentially
In the general case we expect static storage duration variables to be initialized before main, but the compiler is allowed to defer initialization in some situations (the rules are complex see standard for details).
- 注 1:全局变量:
松散地用于指代可能在 之前初始化的静态存储持续时间变量main()
。 - 注 2:潜在的
在一般情况下,我们希望静态存储持续时间变量在 main 之前初始化,但在某些情况下允许编译器推迟初始化(规则很复杂,详见标准)。
回答by Dmitry Khalatov
I expect the constructor order between modules is mainly a function of what order you pass the objects to the linker.
我希望模块之间的构造函数顺序主要取决于将对象传递给链接器的顺序。
However, GCC does let you use init_priority
to explicitly specify the orderingfor global ctors:
但是,GCC 确实允许您使用init_priority
显式指定全局 ctors的顺序:
class Thingy
{
public:
Thingy(char*p) {printf(p);}
};
Thingy a("A");
Thingy b("B");
Thingy c("C");
outputs 'ABC' as you'd expect, but
如您所料,输出“ABC”,但是
Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");
outputs 'BAC'.
输出'BAC'。
回答by Nicholas Smith
Since you already know that you shouldn't rely on this information unless absolutely necessary, here it comes. My general observation across various toolchains (MSVC, gcc/ld, clang/llvm, etc) is that the order in which your object files are passed to the linker is the order in which they will be initialized.
由于您已经知道除非绝对必要,否则不应依赖此信息,因此它来了。我对各种工具链(MSVC、gcc/ld、clang/llvm 等)的一般观察是,将目标文件传递给链接器的顺序就是它们将被初始化的顺序。
There are exceptions to this, and I do not claim to all of them, but here are the ones I ran into myself:
这有例外,我并不声称所有这些都是例外,但这里是我自己遇到的那些:
1) GCC versions prior to 4.7 actually initialize in the reverse order of the link line. This ticket in GCCis when the change happened, and it broke a lot of programs that depended on initialization order (including mine!).
1) 4.7 之前的 GCC 版本实际上以链接线的相反顺序进行初始化。GCC 中的这张票是发生变化的时候,它破坏了许多依赖于初始化顺序的程序(包括我的!)。
2) In GCC and Clang, usage of constructor function prioritycan alter the initialization order. Note that this only applies to functions that are declared to be "constructors" (i.e. they should be run just like a global object constructor would be). I have tried using priorities like this and found that even with highest priority on a constructor function, all constructors without priority (e.g. normal global objects, constructor functions without priority) will be initialized first. In other words, the priority is only relative to other functions with priorities, but the real first class citizens are those without priority. To make it worse, this rule is effectively the opposite in GCC prior to 4.7 due to point (1) above.
2) 在 GCC 和 Clang 中,使用构造函数优先级可以改变初始化顺序。请注意,这仅适用于声明为“构造函数”的函数(即它们应该像全局对象构造函数一样运行)。我尝试过使用这样的优先级,发现即使构造函数具有最高优先级,所有没有优先级的构造函数(例如普通全局对象、没有优先级的构造函数)将首先被初始化。换句话说,优先级只是相对于其他有优先级的功能而言,但真正的一等公民是那些没有优先级的。更糟糕的是,由于上述第 (1) 点,此规则在 4.7 之前的 GCC 中实际上是相反的。
3) On Windows, there is a very neat and useful shared-library (DLL) entry-point function called DllMain(), which if defined, will run with parameter "fdwReason" equal to DLL_PROCESS_ATTACH directly after all global data has been initialized and beforethe consuming application has a chance to call any functions on the DLL. This is extremely useful in some cases, and there absolutely is notanalogous behavior to this on other platforms with GCC or Clang with C or C++. The closest you will find is making a constructor function with priority (see above point (2)), which absolutely is not the same thing and won't work for many of the use cases that DllMain() works for.
3) 在 Windows 上,有一个非常简洁且有用的共享库 (DLL) 入口点函数,称为DllMain(),如果定义了该函数,将在所有全局数据初始化后直接使用等于 DLL_PROCESS_ATTACH 的参数“fdwReason”运行,并且在消费应用程序有机会调用 DLL 上的任何函数之前。这在某些情况下非常有用,并且在使用 GCC 或使用 C 或 C++ 的 Clang 的其他平台上绝对没有与此类似的行为。您会发现最接近的是创建一个具有优先级的构造函数(参见上面的第 (2) 点),这绝对不是一回事,并且不适用于 DllMain() 所适用的许多用例。
4) If you are using CMake to generate your build systems, which I often do, I have found that the order of the input source files will be the order of their resultant object files given to the linker. However, often times your application/DLL is also linking in other libraries, in which case those libraries will be on the link line afteryour input source files. If you are looking to have one of your global objects be the very first oneto initialize, then you are in luck and your can put the source file containing that object to be the first in the list of source files. However, if you are looking to have one be the very last oneto initialize (which can effectively replicate DllMain() behavior!) then you can make a call to add_library() with that one source file to produce a static library, and add the resulting static library as the very last link dependency in your target_link_libraries() call for your application/DLL. Be wary that your global object may get optimized out in this case and you can use the --whole-archiveflag to force the linker not to remove unused symbols for that specific tiny archive file.
4) 如果您使用 CMake 生成构建系统(我经常这样做),我发现输入源文件的顺序将是它们提供给链接器的结果目标文件的顺序。但是,通常您的应用程序/DLL 也会链接到其他库中,在这种情况下,这些库将位于您输入源文件之后的链接行上。如果您希望将其中一个全局对象作为第一个初始化的对象,那么您很幸运,您可以将包含该对象的源文件放在源文件列表中的第一个。但是,如果您希望拥有最后一个初始化(这可以有效地复制 DllMain() 行为!)然后您可以使用该源文件调用 add_library() 以生成静态库,并将生成的静态库添加为 target_link_libraries() 中的最后一个链接依赖项) 调用您的应用程序/DLL。请注意在这种情况下您的全局对象可能会被优化,您可以使用--whole-archive标志强制链接器不要删除该特定小档案文件的未使用符号。
Closing Tip
结束提示
To absolutely know the resulting initialization order of your linked application/shared-library, pass --print-map to ld linker and grep for .init_array (or in GCC prior to 4.7, grep for .ctors). Every global constructor will be printed in the order that it will get initialized, and remember that the order is opposite in GCC prior to 4.7 (see point (1) above).
要完全了解链接的应用程序/共享库的最终初始化顺序,请将 --print-map 传递给 ld 链接器,并将 grep 传递给 .init_array(或在 4.7 之前的 GCC 中,传递给 .ctors 的 grep)。每个全局构造函数都将按照初始化的顺序打印,请记住,在 4.7 之前的 GCC 中顺序是相反的(参见上面的第 (1) 点)。
The motivating factor for writing this answer is that I needed to know this information, had no other choice but to rely on initialization order, and found only sparse bits of this information throughout other SO posts and internet forums. Most of it was learned through much experimentation, and I hope that this saves some people the time of doing that!
写这个答案的动机是我需要知道这些信息,别无选择,只能依赖初始化顺序,并且在其他 SO 帖子和互联网论坛中只发现了这些信息的稀疏部分。大部分是通过大量实验学到的,我希望这可以节省一些人这样做的时间!
回答by Ray Tayek
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12- this link moves around. this oneis more stable but you will have to look around for it.
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12- 此链接四处移动。这一个是比较稳定的,但你将不得不到处寻找它。
edit: osgx supplied a better link.
编辑:osgx 提供了一个更好的链接。
回答by SmacL
In addition to Martin's comments, coming from a C background, I always think of static variables as part of the program executable, incorporated and allocated space in the data segment. Thus static variables can be thought of as being initialised as the program loads, prior to any code being executed. The exact order in which this happens can be ascertained by looking at the data segment of map file output by the linker, but for most intents and purposes the initialisation is simultaeneous.
除了来自 C 背景的 Martin 的评论之外,我一直认为静态变量是程序可执行文件的一部分,在数据段中合并和分配空间。因此,静态变量可以被认为是在程序加载时初始化,在任何代码被执行之前。发生这种情况的确切顺序可以通过查看链接器输出的映射文件的数据段来确定,但对于大多数意图和目的,初始化是同时进行的。
Edit: Depending on construction order of static objects is liable to be non-portable and should probably be avoided.
编辑:根据静态对象的构造顺序可能是不可移植的,应该避免。
回答by Darien Pardinas
If you really want to know the final order I would recommend you to create a class whose constructor logs the current timestamp and create several static instances of the class in each of your cpp files so that you could know the final order of initialization. Make sure to put some little time consuming operation in the constructor just so you don't get the same time stamp for each file.
如果您真的想知道最终顺序,我建议您创建一个类,其构造函数记录当前时间戳,并在每个 cpp 文件中创建该类的几个静态实例,以便您知道初始化的最终顺序。确保在构造函数中放入一些耗时的操作,以免为每个文件获得相同的时间戳。