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

时间:2009-08-13 10:48:24

标签: c++ gcc static global constructor

我正在研究一些C ++代码,我遇到了一个问题,这个问题一直困扰着我...假设我正在Linux主机上用GCC编译ELF目标,那么全局静态构造函数和析构函数叫做?

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

我最感兴趣的是,因为我的代码在运行时被加载,执行然后卸载,所以我可以理解幕后发生的事情。

提前致谢!

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

5 个答案:

答案 0 :(得分:18)

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

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

通常,此初始化发生在main()之前的某个时间。然而,与许多人认为的相反,即使C ++标准无法保证。实际上保证的是,在使用与正被初始化的对象相同的转换单元中定义的任何函数或对象之前完成初始化。请注意,这不是特定于操作系统的。这是C ++规则。以下是标准的引用:

无论是否对象的动态初始化(8.5,9.4,12.1,12.6.1),它都是实现定义的 命名空间范围在main的第一个语句之前完成。如果初始化推迟到某一点 在第一个main语句之后的时间内,它应该在第一次使用任何定义的函数或对象之前发生 在与要初始化的对象相同的翻译单元中

答案 1 :(得分:11)

这不是特定于操作系统,而是特定于编译器。

您已给出答案,初始化在__init完成。

对于第二部分,在gcc中,您可以保证初始化顺序,____attribute____((init_priority(PRIORITY)))附加到变量定义,其中PRIORITY是某个相对值,首先初始化较小的数字。

答案 2 :(得分:10)

这很大程度上取决于编译器和运行时。对构建全局对象的时间做出任何假设都不是一个好主意。

如果您的静态对象依赖于已经构建的另一个静态对象,则这尤其成问题。

这称为“static initialization order fiasco”。即使代码中不是这种情况,关于该主题的C ++ Lite FAQ文章也值得一读。

答案 3 :(得分:6)

您拥有的受助人:

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

如果你有彼此依赖的全局变量,你有两个选择:

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

示例1:Global 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;

示例Global 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;

答案 4 :(得分:5)

根据C ++标准,在使用其翻译单元的任何功能或对象之前调用它们。请注意,对于全局命名空间中的对象,这意味着它们在调用main()之前被初始化。 (请参阅ltcmelo'sMartin's答案,了解详情及对此的讨论。)