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

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

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

有帮助吗?

解决方案

正如您所说,不同编译单元之间的顺序是未定义的。

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

这是因为这不是在语言级别而是在链接器级别解决的。所以你真的需要查看链接器文档。尽管我真的怀疑这是否会有任何有用的帮助。

对于海湾合作委员会:查看 LD

我发现即使更改链接的对象文件的顺序也可以更改初始化顺序。因此,您不仅需要担心链接器,还需要担心构建系统如何调用链接器。即使尝试解决问题也几乎是不可能的。

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

有一些技术可以解决这个问题。

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

  • 注1:全局变量:
    宽松地用于指之前可能初始化的静态存储持续时间变量 main().
  • 笔记2:潜在地
    一般情况下,我们期望静态存储持续时间变量在 main 之前初始化,但在某些情况下允许编译器推迟初始化(规则很复杂,详细信息请参阅标准)。

其他提示

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

但是,GCC会让你使用 init_priority 明确指定全球ctors的排序

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'。

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

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

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

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

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

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

结束提示

要完全了解链接的应用程序/共享库的初始化顺序,请将--print-map传递给ld链接器,将grep传递给.init_array(或者在GCC prio中)

http://www.parashift.com/c++ -faq-lite / ctors.html#faq-10.12 - 此链接四处移动。这个一个更稳定,但你必须环顾四周。

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

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

编辑:根据静态物体的构造顺序,可能是不可携带的,应该避免使用。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top