代码之家  ›  专栏  ›  技术社区  ›  Matthew Iselin

C++:何时(以及如何)C++全局静态构造函数调用?

  •  24
  • Matthew Iselin  · 技术社区  · 17 年前

    我正在研究一些C++代码,我遇到了一个困扰我一段时间的问题…假设我在Linux主机上为ELF目标使用GCC编译,全局静态构造函数和析构函数在哪里调用?

    我听说crtbegin.o中有一个函数init,crtend.o中有一个函数fini。这些是由crt0.o调用的吗?或者动态链接器是否实际检测到它们在加载的二进制文件中的存在并调用它们?如果是,, 什么时候

    我主要想知道,这样我就可以理解在运行时加载、执行和卸载代码时幕后发生了什么。

    更新:我基本上是想弄清楚调用构造函数的一般时间。我不想在我的代码中根据这些信息做出假设,这或多或少是为了更好地理解程序加载时在较低级别发生的事情。我知道这是非常特定于操作系统的,但在这个问题上,我试图缩小范围。

    5 回复  |  直到 17 年前
        1
  •  21
  •   Leandro T. C. Melo    17 年前

    当谈到非局部静态对象时,没有太多的保证。正如您已经知道的(这里也提到过),它不应该编写依赖于此的代码。静态初始化命令失败。。。

    静态对象经过两个阶段的初始化:静态初始化和动态初始化。前者首先发生并执行零初始化或常量表达式初始化。后者发生在所有静态初始化完成之后。例如,这就是调用构造函数的时候。

    通常,此初始化发生在main()之前的某个时间。然而,与许多人所想的相反,即使是C++标准也不能保证。实际上可以保证的是,初始化是在使用与被初始化对象在同一翻译单元中定义的任何函数或对象之前完成的。请注意,这不是特定于操作系统的。这是C++规则。这里引用了《标准》中的一段话:

    它是实现定义的对象的动态初始化(8.5、9.4、12.1、12.6.1)与否 命名空间范围在main的第一条语句之前完成。如果初始化延迟到某个时间点 在与要初始化的对象相同的转换单元中
        2
  •  12
  •   ebo    17 年前

    这主要取决于编译器和运行时。对构建全局对象的时间进行任何假设都不是一个好主意。

    如果有一个静态对象依赖于另一个已经构建的对象,那么这尤其是一个问题。

    这被称为“ static initialization order fiasco “。即使代码中不是这样,关于该主题的C++Lite常见问题解答文章也值得一读。

        3
  •  11
  •   Vadim Kotov First Zero    8 年前

    您已经给出了答案,初始化是在 __init .

    对于第二部分,在gcc中,可以使用 ____attribute____((init_priority(PRIORITY))) 附加到变量定义,其中 PRIORITY

        4
  •  7
  •   Loki Astari    17 年前

    您拥有的受让人:

    • 全局命名空间中的所有静态非本地对象都在main()之前构造
    • 另一个名称空间中的所有静态非本地对象都是在使用该名称空间中的任何函数/方法之前构造的(因此允许编译器对它们进行潜在的惰性计算[但不要指望这种行为])。
    • 翻译单元中的所有静态非局部对象都是按照声明的顺序构造的。
    • 翻译单位之间的顺序没有定义。
    • 所有静态非本地对象都将按与创建相反的顺序销毁。(这包括静态函数变量(第一次使用时延迟创建)。

    如果有相互依赖的全局变量,则有两个选项:

    • 把它们放在同一个翻译单元中。
    • 将它们转换为首次使用时检索和构造的静态函数变量。

    示例1:全局A的构造函数使用全局日志

    class AType
    {    AType()  { log.report("A Constructed");}};
    
    LogType    log;
    AType      A;
    
    // Or 
    Class AType() 
    {    AType()  { getLog().report("A Constructed");}};
    LogType& getLog()
    {
        static LogType  log;
        return log;
    }
    // Define A anywhere;
    

    示例全局B的析构函数使用全局日志

    在这里,您必须授权在对象B之前不销毁对象日志。这意味着必须在对象B之前完全构造日志(因为销毁规则的相反顺序将适用)。同样,也可以使用相同的技术。要么将它们放在同一个翻译单元中,要么使用函数获取日志。

    class BType
    {    ~BType()  { log.report("B Destroyed");}};
    
    LogType    log;
    BType      B;   // B constructed after log (so B will be destroyed first)
    
    // Or 
    Class BType() 
    {    BType()    { getLog();}
         /*
          * If log is used in the destructor then it must not be destroyed before B
          * This means it must be constructed before B 
          * (reverse order destruction guarantees that it will then be destroyed after B)
          *
          * To achieve this just call the getLog() function in the constructor.
          * This means that 'log' will be fully constructed before this object.
          * This means it will be destroyed after and thus safe to use in the destructor.
          */
        ~BType()    { getLog().report("B Destroyed");}
    };
    LogType& getLog()
    {
        static LogType  log;
        return log;
    }
    // Define B anywhere;
    
        5
  •  5
  •   Community Mohan Dere    9 年前

    main() 被称为。(见 ltcmelo's Martin's 有关mote详细信息的解答以及对此的讨论。)