28

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

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

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

提前致谢!

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

4

5 回答 5

21

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

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

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

命名空间范围的对象的动态初始化(8.5、9.4、12.1、12.6.1)是否在main的第一条语句之前完成是实现定义的。如果初始化推迟到 main 的第一个语句之后的某个时间点,它应该发生在第一次使用与要初始化的对象在同一翻译单元中定义的任何函数或对象之前
于 2009-08-13T12:36:56.560 回答
12

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

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

这被称为“静态初始化命令惨败”。即使您的代码中并非如此,有关该主题的 C++Lite 常见问题解答文章也值得一读。

于 2009-08-13T10:57:07.183 回答
11

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

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

对于第二部分,在 gcc 中,您可以通过__attribute__((init_priority(PRIORITY)))附加到变量定义来保证初始化的顺序,其中PRIORITY是一些相对值,首先初始化较低的数字。

于 2009-08-13T12:33:42.583 回答
8

The grantees you have:

  • All static non-local objects in the global namespace are constructed before main()
  • All static non-local objects in another namespace are constructed before any functions/methods in that namespace are used (Thus allowing the compiler to potentially lazy evaluate them [but don't count on this behavior]).
  • All static non-local objects in a translation unit are constructed in the order of declaration.
  • Nothing is defined about the order between translation units.
  • All static non-local objects are destroyed in the reverse order of creation. (This includes the static function variables (which are lazily created on first use).

If you have globals that have dependencies on each other you have two options:

  • Put them in the same translation unit.
  • Transform them into static function variables retrieved and constructed on first use.

Example 1: Global A's constructor uses Global log

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;

Example Global B's destructor uses Global log

Here you have to grantee that the object log is not destroyed before the object B. This means that log must be fully constructed before B (as the reverse order of destruction rule will then apply). Again the same techniques can be used. Either put them in the same translation unit or use a function to get log.

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;
于 2009-08-13T16:57:00.003 回答
5

根据 C++ 标准,在使用其翻译单元的任何函数或对象之前调用它们。请注意,对于全局命名空间中的对象,这意味着它们在main()被调用之前被初始化。(有关微粒的详细信息和对此的讨论,请参见ltcmeloMartin 的答案。)

于 2009-08-13T12:12:58.010 回答