静态变量初始化顺序

Dmi*_*tov 60 c++ linker static gcc visual-studio

C++保证编译单元(.cpp文件)中的变量按声明顺序初始化.对于编译单元的数量,此规则分别适用于每个(我的意思是类外的静态变量).

但是,变量的初始化顺序在不同的编译单元中是不确定的.

我在哪里可以看到关于gcc和MSVC的这个订单的一些解释(我知道依赖于这是一个非常糟糕的想法 - 它只是为了理解我们在迁移到新的GCC主要和不同操作系统时遗留代码可能遇到的问题) ?

Mar*_*ork 69

正如您所说,订单在不同的编译单元中是未定义的.

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

这是因为这不是在语言级别解决,而是在链接器级别解决.所以你真的需要查看链接器文档.虽然我真的怀疑这将有助于任何有用的方式.

对于gcc:查看ld

我发现即使更改链接的对象文件的顺序也可以更改初始化顺序.因此,您需要担心的不仅仅是链接器,而是构建系统如何调用链接器.即使尝试解决问题实际上也是一个不起眼的问题.

这通常只是在初始化全局时在初始化期间相互引用的问题(因此仅影响具有构造函数的对象).

有解决问题的技巧.

  • 延迟初始化.
  • 施瓦茨柜台
  • 将所有复杂的全局变量放在同一个编译单元中.

  • 优选地,根本不需要全局变量. (11认同)
  • 我自己的偏好适用于同一个编译单元中的所有全局变量...... :-) (7认同)
  • 你们两个都很对,但不幸的是,对于那些编写了大量图书馆和我们必须使用的第三方代码的程序员而言,这是不为人知的...... (4认同)
  • @AdN:是的,静态和动态初始化之间存在差异.静态发生在动态之前(因为它在编译时完成并烘焙到底层程序集段(BSS块等)).当人们谈论初始化顺序时,我们只是指动态部分(在运行时必须执行代码的部分进行初始化(C++ 11`constexpt`基本上是另一个编译器时间常数)).这不应该导致任何关于此的论点改变或导致某些事情咬你.您能否提出问题,以便我们更详细地探讨. (4认同)

小智 18

我希望模块之间的构造函数顺序主要取决于将对象传递给链接器的顺序.

然而,GCC不会让你使用init_priority显式指定的顺序全球构造函数:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");
Run Code Online (Sandbox Code Playgroud)

按照你的预期输出"ABC",但是

Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");
Run Code Online (Sandbox Code Playgroud)

输出'BAC'.

  • 一个新的[明确指定](http://www.fifi.org/cgi-bin/info2www?%28gcc-300%29C%2B%2B%20Attributes)链接现在有效. (2认同)

Nic*_*ith 13

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

这有例外,我没有声称所有这些,但这里是我遇到的那些:

1)4.7之前的GCC版本实际上以链接线的相反顺序初始化.GCC中的这张票是发生变化的时候,它打破了许多依赖于初始化顺序的程序(包括我的!).

2)在GCC和Clang中,构造函数优先级的使用可以改变初始化顺序.请注意,这仅适用于声明为"构造函数"的函数(即它们应该像全局对象构造函数一样运行).我已经尝试使用这样的优先级,并发现即使在构造函数上具有最高优先级,所有没有优先级的构造函数(例如,普通的全局对象,没有优先级的构造函数)都将首先被初始化.换句话说,优先权仅相对于具有优先权的其他职能,但真正的头等公民是没有优先权的人.更糟糕的是,由于上述第(1)点,此规则实际上与4.7之前的GCC相反.

3)在Windows上,有一个非常简洁有用的共享库(DLL)入口点函数,称为DllMain(),如果已定义,将在所有全局数据初始化后直接使用等于DLL_PROCESS_ATTACH的参数"fdwReason"运行.消费应用程序有机会调用DLL上的任何函数之前.这在某些情况下非常有用,并且在使用GCC或Clang with C或C++的其他平台上绝对没有类似的行为.你会发现最接近的是构造函数具有优先级(参见上面的第(2)点),这绝对不是同一个东西,并且不适用于DllMain()适用的许多用例.

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

关闭提示

要完全了解链接的应用程序/共享库的初始化顺序,请将--print-map传递给ld链接器,将grep传递给.init_array(或者在4.7之前的GCC中,将grep传递给.ctors).每个全局构造函数将按其初始化的顺序打印,并记住在4.7之前的GCC中的顺序相反(参见上面的第(1)点).

写这个答案的动机因素是我需要知道这些信息,除了依赖初始化顺序之外别无选择,并且在其他SO帖子和互联网论坛中只发现了这些信息的稀疏位.其中大部分是通过大量实验学到的,我希望这可以节省一些人的时间!