Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/149.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 静态变量初始化顺序_C++_Visual Studio_Gcc_Static_Linker - Fatal编程技术网

C++ 静态变量初始化顺序

C++ 静态变量初始化顺序,c++,visual-studio,gcc,static,linker,C++,Visual Studio,Gcc,Static,Linker,C++保证编译单元(.cpp文件)中的变量按照声明的顺序进行初始化。对于编译单元的数量,这个规则分别适用于每个单元(我指的是类外的静态变量) 但是,变量的初始化顺序在不同的编译单元中没有定义 我在哪里可以看到一些关于gcc和MSVC的顺序的解释(我知道依赖于它是一个非常糟糕的主意-这只是为了理解在迁移到新的gcc主要和不同的操作系统时,我们可能会遇到的遗留代码问题)?正如您所说,顺序在不同的编译单元之间是未定义的 在同一个编译单元中,顺序定义良好:与定义的顺序相同 这是因为这不是在语言级别解决的

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

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


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

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

在同一个编译单元中,顺序定义良好:与定义的顺序相同

这是因为这不是在语言级别解决的,而是在链接器级别解决的。因此,您确实需要查看链接器文档。虽然我真的怀疑这会有什么帮助

对于gcc:请查看ld

我发现,即使更改链接的对象文件的顺序,也会更改初始化顺序。因此,您需要担心的不仅仅是链接器,还包括构建系统如何调用链接器。即使试图解决这个问题,实际上也是行不通的

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

有一些技巧可以解决这个问题。
  • 延迟初始化
  • 将所有复杂的全局变量放在同一个编译单元中

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

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

但是,GCC确实允许您使用全局CTOR:

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

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

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

-此链接会四处移动。这是更稳定,但你将不得不四处寻找它


编辑:osgx提供了一个更好的方法。

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

因为您已经知道,除非绝对必要,否则不应该依赖这些信息,下面就是它。我对各种工具链(MSVC、gcc/ld、clang/llvm等)的一般观察结果是,对象文件传递到链接器的顺序就是它们初始化的顺序

这方面也有例外,我并不要求所有例外,但以下是我自己遇到的例外:

1) 4.7之前的GCC版本实际上以链接行的相反顺序初始化。就是当改变发生时,它破坏了许多依赖于初始化顺序的程序(包括我的!)

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

3) 在Windows上,有一个名为的非常整洁且有用的共享库(DLL)入口点函数,如果定义了该函数,将在初始化所有全局数据之后以及消费应用程序有机会调用DLL上的任何函数之前,直接使用等于DLL_PROCESS_ATTACH的参数“fdhreason”运行该函数。在某些情况下,这是非常有用的,在GCC或Clang的其他平台上,C或C++绝对没有类似的行为。您将发现的最接近的方法是生成具有优先级的构造函数(请参见上面的第(2)点),这绝对不是同一件事,而且对于DllMain()所适用的许多用例来说都不起作用

4) 如果您使用CMake生成构建系统(我经常这样做),我发现输入源文件的顺序将是它们的结果对象文件给链接器的顺序。但是,通常情况下,应用程序/DLL也链接到其他库中,在这种情况下,这些库将位于输入源文件之后的链接行上。如果您希望让一个全局对象成为第一个初始化的对象,那么您很幸运,您可以将
// Foo.h
class Foo {
 public:
  Foo() {}

  static bool insertIntoBar(int number);

 private:
  static std::vector<int>& getBar();
};

// Foo.cpp
std::vector<int>& Foo::getBar() {
  static std::vector<int> bar;
  return bar;
}

bool Foo::insertIntoBar(int number) {
  getBar().push_back(number);
  return true;
}

// A.h
class A {
 public:
  A() {}

 private:
  static bool a1;
};

// A.cpp
bool A::a1 = Foo::insertIntoBar(22);