代码之家  ›  专栏  ›  技术社区  ›  Dmitry Khalatov

静态变量初始化顺序

  •  56
  • Dmitry Khalatov  · 技术社区  · 17 年前

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

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

    6 回复  |  直到 12 年前
        1
  •  76
  •   Community Mohan Dere    6 年前

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

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

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

    ld

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

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

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

    • 延迟初始化。
    • Schwarz Counter
    • 将所有复杂的全局变量放在同一个编译单元中。

    • 注1:全球:
      main() .
    • 注2:潜在的
      在一般情况下,我们希望静态存储持续时间变量在main之前初始化,但在某些情况下允许编译器延迟初始化(规则很复杂,有关详细信息,请参见标准)。
        2
  •  20
  •   underscore_d    10 年前

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

    但是,GCC确实允许您 use init_priority to explicitly specify the ordering 对于全球贸易部门:

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

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

        3
  •  17
  •   Nicholas Smith    10 年前

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

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

    This ticket in GCC 就是当改变发生时,它破坏了许多依赖于初始化顺序的程序(包括我的!)。

    2) 在GCC和Clang中,使用 constructor function priority 第一 . 换言之,优先权只是相对于具有优先权的其他职能而言的,但真正的一等公民是那些没有优先权的人。更糟糕的是,由于上述第(1)点,该规则在GCC 4.7之前实际上是相反的。

    3) 在Windows上,有一个非常整洁和有用的共享库(DLL)入口点函数,名为 DllMain() ,如果已定义,将在初始化所有全局数据并 之前 消费应用程序有机会调用DLL上的任何函数。在某些情况下,这是非常有用的,而且是绝对有用的

    之后 您的输入源文件。如果您希望将其中一个全局对象作为 第一个 若要初始化,您很幸运,您可以将包含该对象的源文件放在源文件列表的第一位。然而,如果你想拥有一个 要初始化(这可以有效地复制DllMain()行为!),您可以使用该源文件调用add_library()生成一个静态库,并将生成的静态库添加为应用程序/DLL的目标_link_libraries()调用中的最后一个链接依赖项。请注意,在这种情况下,您的全局对象可能会得到优化,您可以使用 --whole-archive 用于强制链接器不删除该特定微小存档文件的未使用符号的标志。

    闭合端

    要完全了解链接应用程序/共享库的初始化顺序,请将--print map传递给ld linker和grep for.init_数组(或在4.7之前的GCC中,grep for.ctors)。每个全局构造函数都将按照初始化的顺序打印,记住GCC中4.7之前的顺序是相反的(见上文第(1)点)。

    写这个答案的动机是,我需要知道这些信息,别无选择,只能依赖于初始化顺序,并且在其他SO帖子和互联网论坛中只能找到少量的信息。其中大部分都是通过大量的实验学习到的,我希望这可以节省一些人这样做的时间!

        4
  •  4
  •   Ray Tayek    10 年前

    http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 -这个链接会四处移动。这 one 它更稳定,但你必须四处寻找。

    编辑:osgx提供了更好的 link

        5
  •  2
  •   Moritz    6 年前

    一个健壮的解决方案是使用getter函数返回对静态变量的引用。下面显示了一个简单的示例,这是我们的 SDG Controller middleware .

    // 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);
    

    初始化将仅使用静态成员变量 bool A::a1 Foo::insertIntoBar(22) . 然后,这将调用 Foo::getBar() 其中静态 std::vector<int>

    如果 static std::vector<int> bar Foo class ,根据源文件的命名顺序,有可能 bar insertIntoBar() 被调用,从而使程序崩溃。

    插入条() 在初始化过程中,顺序将不取决于源文件的名称(即随机文件),而是取决于 标准::向量<int>

        6
  •  1
  •   SmacL    17 年前

    编辑:根据静态对象的构造顺序,可能会导致不可移植,因此可能应该避免。

        7
  •  0
  •   Darien Pardinas    11 年前

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

    推荐文章