【问题标题】:Destructor of a static object constructed within the destructor of another static object在另一个静态对象的析构函数中构造的静态对象的析构函数
【发布时间】:2012-12-26 00:37:18
【问题描述】:

我的析构函数有一些问题,在下一个代码中:

#include <stdlib.h>
#include <cstdio>

class Foo2
{
    public:
        Foo2() { printf("foo2 const\n"); }

        ~Foo2()
        {
            printf("foo2 dest\n"); //  <--- wasn't called for bionic libc
        }
};

static Foo2& GetFoo2()
{
    static Foo2 foo2;
    printf ("return foo2\n");
    return foo2;
}

class Foo1
{
    public:
        Foo1() { printf("foo1 const\n"); }

        ~Foo1()
        {
            printf("foo1 dest\n");
            GetFoo2();
        }
};

int main( int argc, const char* argv[] )
{
        printf("main 1 \n");
        static Foo1 anotherFoo;
        printf("main 2 \n");
}

为什么没有为bionic 调用foo2 的析构函数,而是为glibc 调用析构函数?

编辑
仿生的输出:

main 1  
foo1 const  
main 2  
foo1 dest  
foo2 const  
return foo2  

调试信息:

(gdb) break 22
Breakpoint 1 at 0x8048858: file test.C, line 22.
(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x08048858 in Foo2::~Foo2() at test.C:22
(gdb) cont
[    exited with code 0]

【问题讨论】:

  • 什么是仿生和glibc?
  • @LuchianGrigore 两种流行的标准 C 和 C++ 库实现:glibc 是 GNU 的,Bionic 是用于 Android 的。
  • 我认为这是 Bionic 中的一个错误
  • 好像是仿生的一个bug……

标签: c++ oop static g++ destructor


【解决方案1】:

我认为您的代码具有未定义的行为,尽管标准 不是很清楚(或者我在 标准)。您的代码在 静态对象的析构函数。该标准未解决 这种情况,但是:

  1. 确实说必须反向调用析构函数 施工顺序。在您的情况下,这意味着 GetFoo2 中的静态对象必须在它被销毁之前被破坏 构造,这是自相矛盾的。

  2. 第 3.6/3 节中的文字描述了析构函数的顺序 以及在atexit 注册的函数。要求是 这样每个人都必须使用相同的注册机制。 一旦你调用了exit(或从 main) 是未定义的行为。

  3. 还有§3.6/2,它说“如果一个函数包含 静态或线程存储持续时间的块范围对象 已被销毁,并且该函数在 使用静态或线程存储销毁对象 持续时间,程序有未定义的行为,如果流 控制通过先前的定义 破坏了blockscope对象。”这句话讲了alread 被破坏的物体,但并不需要太多的想象力去思考 没有“尚未构造”的对象只是一个 监督。

最后,我想说我上面的第一点是结论性的 关于意图。在第 1.3.24 节中,有一个注释(非规范, 但表示意图)“可能会出现未定义的行为 当本国际标准省略任何明确的定义时 行为或当程序使用错误的构造或 错误的数据。”在这种情况下,唯一的描述 所需的行为是不可能的(因为你不能破坏 对象在构造之前),标准什么也没说 关于如何解决这个问题。

【讨论】:

  • 我不太相信。 (1) 我认为其意图是好像所有的静态都在一个堆栈上,这不会给这段代码带来问题。 (2) 我不认为静态析构函数使用 atexit。我认为这是定义明确的行为。
  • 我可能同意基于 §1.3.24 的未定义,但仅此而已。我在 §3.6 [终止] 中没有看到任何(在 n3337 中)涉及手头的具体情况。逐字逐句地解决的是,如果一个对象是在另一个之后构造的,那么它的析构函数应该首先被调用,但是我们通常在析构函数的执行过程中构造对象......
  • 我没有看到任何未定义的行为或矛盾。 Foo1 的生命周期已经结束(因为它的析构函数调用开始),所以 Foo2 的析构函数应该运行,它应该在 Foo1 的析构函数之后运行。
【解决方案2】:

我在这段代码中看到的所有实例都是静态的。

因此,它们的析构函数在可执行文件结束时调用,在 main 完成后。

如果没有调用析构函数,那么这是一个错误。

【讨论】:

  • 在我看来 foo2 在程序终止之前不会被构造,此时 anotherFoo 被破坏。
  • 我看到的所有实例都不应该在main() 之前构建。它们都是局部静态,应该在第一次进入作用域时构建。
  • 你给我带来了一些疑惑,我去检查一下静态实例的构造……让我们确定一下。
  • @JasonD 和 JamesKanze 你是对的。参照。 stackoverflow.com/questions/3063027/… 。我将相应地编辑我的答案。谢谢你指出我这一点。
【解决方案3】:

当程序存在时,静态对象将被销毁。在~Foo2() 放置一个断点,您会看到它或将日志写入文件应该可以帮助您诊断它。如果真的没有被调用,那么它是一个编译器错误。

上传图片回答问题也很有趣。

【讨论】:

  • 这很有趣,但是当编译器不同意时,答案应该参考更高的权威来支持他们的主张:标准。
【解决方案4】:

C++11 3.6.3/1:具有静态存储持续时间的已初始化对象的析构函数 [...] 作为从 main 返回的结果被调用

在程序从main返回时,anotherFoo已经被初始化;但是foo2 没有,因为直到在anotherFoo 的销毁期间第一次调用GetFoo2 时它才被初始化。因此,对规则的严格解释意味着不应调用其析构函数。

【讨论】:

  • 或者说它的析构函数应该在构造函数之前调用。管他呢。我认为这是未定义行为的情况。
  • @JamesKanze:这当然并不意味着应该在构造函数之前调用析构函数:“析构函数用于初始化对象 [...]被调用”。跨度>
  • 并且对象在你离开程序之前被初始化。 (但我同意该标准对所需的行为完全不明确。这就是为什么我认为它是未定义的行为。)
  • @JamesKanze:“由于main返回而被调用”,(或调用std::exit,但这与这里无关),而不是因为离开程序。正如我所说,当程序从main返回时,它没有被初始化。
猜你喜欢
  • 1970-01-01
  • 2014-03-23
  • 2017-03-17
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 2011-03-09
相关资源
最近更新 更多