静态variables初始化顺序

C ++保证编译单元(.cpp文件)中的variables按照声明的顺序进行初始化。 对于编译单元的数量,这个规则分别适用于每一个(我是指类之外的静态variables)。

但是,variables初始化的顺序在不同的编译单元中是不确定的。

我在哪里可以看到关于gcc和MSVC的这个命令的一些解释(我知道依靠这是一个非常糟糕的主意 – 这只是为了理解我们在移植到新的GCC主要和不同的操作系统时可能会遇到的问题) ?

正如你所说,在不同编译单元中的顺序是未定义的。

在同一个编译单元中,顺序是明确定义的:与定义相同的顺序。

这是因为这不是在语言层面解决,而是在链接层面。 所以你真的需要检查链接文件。 虽然我真的怀疑这将有助于任何有用的方式。

对于gcc:查看ld

我发现即使改变被链接的目标文件的顺序也可以改变初始化顺序。 所以,不仅仅是你需要担心的连接器,而且连接器是如何被你的构build系统调用的。 即使试图解决这个问题实际上是一个不起眼的问题。

这通常只是初始化在自己的初始化过程中相互引用的全局问题(所以只影响具有构造函数的对象)。

有技巧来解决这个问题。

  • 懒惰初始化。
  • 施瓦茨计数器
  • 将所有复杂的全局variables放在同一个编译单元中。

我期望模块之间的构造函数顺序主要是按照什么顺序将对象传递给链接器的函数。

但是,GCC可以让你使用init_priority来显式地指定全局的顺序 :

 class Thingy { public: Thingy(char*p) {printf(p);} }; Thingy a("A"); Thingy b("B"); Thingy c("C"); 

输出'ABC',如你所料,但是

 Thingy a __attribute__((init_priority(300))) ("A"); Thingy b __attribute__((init_priority(200))) ("B"); Thingy c __attribute__((init_priority(400))) ("C"); 

输出“BAC”。

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 – 此链接移动。 这一个更稳定,但你将不得不四处寻找。

编辑:osgx提供了一个更好的链接 。

既然你已经知道你不应该依赖这个信息,除非绝对必要,在这里。 我对各种工具链(MSVC,gcc / ld,clang / llvm等)的一般观察是,将对象文件传递给链接器的顺序是它们将被初始化的顺序。

有一些例外,我不要求所有这些,但这些是我自己遇到的:

1)4.7之前的GCC版本实际上是以链接线的相反顺序初始化的。 海湾合作委员会的这张票是发生变化的时候,它打破了很多依赖于初始化顺序(包括我的!)的程序。

2)在GCC和Clang中,使用构造函数的优先级可以改变初始化顺序。 请注意,这只适用于声明为“构造函数”的函数(即它们应该像全局对象构造函数那样运行)。 我尝试过使用像这样的优先级,并发现,即使在构造函数的最高优先级,所有没有优先级的构造函数(例如普通的全局对象,没有优先级的构造函数)将被初始化。 换句话说,优先权只是与其他有优先权的职能有关,而真正的一stream公民是没有优先权的。 更糟糕的是,由于上述第(1)点,这个规则在4.7之前在GCC中恰恰相反。

3)在Windows上,有一个名为DllMain()的非常整齐有用的共享库(DLL)入口点函数,如果定义了它,将在所有全局数据初始化之后直接运行参数“fdwReason”等于DLL_PROCESS_ATTACH。 使用应用程序之前有机会调用DLL上的任何函数。 这在某些情况下是非常有用的,在其他使用C或C ++的GCC或Clang平台上绝对没有类似的行为。 你会发现最接近的是build立一个具有优先级的构造函数(参见上面的第(2)点),这绝对不是一回事,也不适用于DllMain()工作的许多用例。

4)如果你正在使用CMake来生成你的构build系统,我经常这样做,我发现input源文件的顺序将是它们给链接器的结果对象文件的顺序。 但是,通常你的应用程序/ DLL也会链接到其他库中,在这种情况下,这些库将在input源文件之后的链接行中。 如果您正在寻找一个全局对象作为第一个初始化对象,那么您很幸运,您可以将包含该对象的源文件放在源文件列表中的第一个。 但是,如果你正在寻找一个最后一个初始化(它可以有效地复制DllMain()行为!),那么你可以用一个源文件调用add_library()来产生一个静态库,并添加得到的静态库作为您的应用程序/ DLL的target_link_libraries()调用中的最后一个链接依赖项。 要小心,在这种情况下,您的全局对象可能会被优化,您可以使用–whole-archive标志来强制链接器不要删除该特定的微型归档文件的未使用的符号。

closures提示

要绝对知道链接的应用程序/共享库的结果初始化顺序,请将–print-map传递给ld链接器,grep传递给.init_array(或者GCC之前的4.7,grep for .ctors)。 每个全局构造函数都将按照它将被初始化的顺序来打印,并记住在4.7之前的GCC中的顺序是相反的(参见上面的点(1))。

写这个答案的动机是我需要知道这个信息,别无select,只能依赖初始化顺序,而在其他SOpost和networking论坛中只发现这些信息的稀疏位。 大部分是通过大量的实验学到的,我希望这样做可以节省一些人的时间!

除了来自C背景的Martin的评论之外,我总是将静态variables视为程序可执行文件的一部分,将数据段中的空间合并和分配。 因此,在执行任何代码之前,可以将静态variables视为在程序加载时被初始化。 通过查看链接器输出的映射文件的数据段,可以确定发生这种情况的确切顺序,但是对于大多数意图和目的,初始化是同时发生的。

编辑:根据静态对象的构造顺序可能是不可移植的,应该可以避免。

如果你真的想知道最终的顺序,我build议你创build一个类的构造函数logging当前的时间戳,并在每个cpp文件中创build类的静态实例,以便你可以知道最终的初始化顺序。 确保在构造函数中进行一些耗时的操作,这样就不会为每个文件获得相同的时间戳。