【问题标题】:Under what circumstances are C++ destructors not going to be called?在什么情况下不调用 C++ 析构函数?
【发布时间】:2011-03-11 21:43:47
【问题描述】:

我知道我的析构函数是在正常展开堆栈和抛出异常时调用的,但不是在调用 exit() 时。

还有其他情况我的析构函数不会被调用吗?诸如 SIGINT 或 SIGSEGV 之类的信号呢?我假设对于 SIGSEGV,它们不会被调用,但对于 SIGNINT 它们是,我怎么知道哪些信号会展开堆栈?

还有其他情况不会被调用吗?

【问题讨论】:

  • 正如这里所指出的,thedailywtf.com/Articles/My-Tales.aspx,您还应该知道,拔出电源插头时不会调用析构函数;)。
  • SIGINT 不会展开堆栈,除非您安装了覆盖默认行为的信号处理程序。默认情况下,SIGINT 会导致程序立即终止。
  • 不将此作为答案发布,因为它似乎更像是问题的疏忽。只有在具有静态、自动或线程存储持续时间的对象的生命周期结束时才会自动调用析构函数(在正常情况下)。对于具有 动态 存储持续时间的对象,仅当在指向对象的指针上调用 delete 时才调用析构函数。因此,对于从未调用过delete 的动态对象,不会调用析构函数(无论是因为内存泄漏使这成为不可能,还是由于疏忽)。

标签: c++ exception signals destructor exit


【解决方案1】:

abort 终止程序而不执行标准所说的自动或静态存储持续时间的对象的析构函数。对于其他情况,您应该阅读特定于实现的文档。

【讨论】:

    【解决方案2】:

    信号本身不会影响当前线程的执行,因此也不会影响析构函数的调用,因为它是一个不同的执行上下文,有自己的堆栈,您的对象不存在的地方。这就像一个中断:它在执行上下文之外的某个地方处理,如果处理,控制权将返回给您的程序。

    与多线程一样,C++ 语言不知道信号的概念。这两者完全相互正交,并由两个不相关的标准指定。它们如何交互取决于实现,只要它不违反任何一个标准。

    附带说明,另一种情况是对象的析构函数不会被调用,即其构造函数抛出异常。不过,成员的析构函数仍会被调用。

    【讨论】:

      【解决方案3】:

      C++ 标准没有说明必须如何处理特定信号——许多实现可能不支持SIGINT 等。如果调用exit()abort()terminate(),则不会调用析构函数。

      编辑:我刚刚快速搜索了 C++ 标准,但我找不到任何指定信号如何与对象生命周期交互的东西——也许有人比我有更好的标准找东西?

      进一步编辑:在回答另一个问题时,我在标准中发现了这一点:

      在退出范围时(但是 完成),析构函数(12.4)是 调用所有构造的对象 具有自动存储期限 (3.7.2) (命名对象或临时对象) 在该范围内声明的,在 他们的相反顺序 宣言。

      所以似乎必须在收到信号时调用析构函数。

      【讨论】:

      • 收到信号后,程序控制不会退出范围。所以引用的标准不适用。 POSIX 信号处理程序的默认行为不执行任何堆栈展开或销毁。
      • @karunski 如果安装了信号处理程序,它肯定会退出范围。
      • @Neil Butterworth 这是 POSIX 标准信号处理程序:opengroup.org/onlinepubs/007908775/xsh/signal.h.html
      • @karunski POSIX 没有定义 C++ 语言。
      • @karunski 但问题是关于 C++ 的。
      【解决方案4】:

      基本上有两种情况会调用析构函数:在函数结束时(或异常时)堆栈展开,如果有人(或引用计数器)调用删除。

      在静态对象中可以找到一种特殊情况——它们在程序结束时通过 at_exit 销毁,但这仍然是第二种情况。

      哪个信号离开 at_exit 可能取决于,kill -9 会立即终止进程,其他信号会告诉它退出,但具体如何取决于信号回调。

      【讨论】:

        【解决方案5】:

        另一种不会调用它们的情况是,如果您使用多态性并且没有将基本析构函数设为虚拟。

        【讨论】:

        • 在这种情况下,您将得到未定义的行为。
        【解决方案6】:

        还有其他情况不会调用它们[析构函数]?

        1. 长跳转:这些会干扰自然堆栈展开过程,并经常导致 C++ 中未定义的行为。
        2. 过早退出(您已经指出了这些,但值得注意的是,在由于抛出异常而已经展开堆栈时抛出会导致未定义的行为,这就是我们永远不应该抛出 dtor 的原因)
        3. 从构造函数中抛出不会调用类的 dtor。这就是为什么,如果您在 ctor 中分​​配由多个不同指针(而不是智能指针)管理的多个内存块,则需要使用函数级 try 块或避免使用初始化器列表并在 ctor 中有一个 try/catch 块body(或者更好的是,只需使用像 scoped_ptr 这样的智能指针,因为到目前为止在初始化列表中成功初始化的任何成员都将被销毁,即使不会调用类 dtor)。
        4. 正如所指出的,当通过基指针删除类时未能将 dtor 设为虚拟可能无法调用子类 dtor(未定义行为)。
        5. 未能为 operator new/new[] 调用调用匹配的 operator delete/delete[](未定义的行为 - 可能无法调用 dtor)。
        6. 在 deallocate 部分中使用带有自定义内存分配器的placement new 时无法手动调用dtor。
        7. 使用像 memcpy 这样的函数,它只将一个内存块复制到另一个内存块而不调用复制 ctor。 mem* 函数在 C++ 中是致命的,因为它们会压倒类的私有数据、覆盖 vtable 等。结果通常是未定义的行为。
        8. 在不完整类型上实例化一些智能指针 (auto_ptr),请参见 discussion

        【讨论】:

        • 不错的列表,但第 3 点有一个缺陷:您实际上可以在初始化列表周围放置一个 try 块,查找函数级别的 try 块:struct X { X() try : x_(42) {} catch (...) {} private: int x_; }; 实际上您可以将它用于任何函数像void foo() try {} catch (...) {},但一些重要的编译器(VS2008,不知道这是否在以后的版本中修复)阻塞了它。不过,关于智能指针的建议仍然有效。
        • @Fabio 谢谢Fabio,我会指出来的!我不知道这些函数级别的 try/catch 块(宁愿在任何地方都使用 RAII,因为它可以缓解头痛)。
        • 总的来说,一个不错的列表。但是,#4 和#5 在技术上是未定义的行为,因此标准对是否调用析构函数没有任何规定。大多数编译器都会像你说的那样运行(或者可能只是崩溃)。
        • 函数try在 GOTW #66 中有说明(今天刚刚向同事指出...巧合...)
        • 感谢@FabioFracassi 提供有关功能级别尝试块的提示。我不敢相信我已经使用 C++ 15 年了,要么忘记了,要么从未遇到过这个特性。
        【解决方案7】:

        如果一个函数或方法有一个 throws 规范,并且抛出了该规范未涵盖的东西,则默认行为是立即退出。堆栈没有展开,也没有调用析构函数。

        POSIX 信号是特定于操作系统的构造,没有 C++ 对象范围的概念。通常你不能对信号做任何事情,除了可能,捕获它,设置一个全局标志变量,然后在信号处理程序退出后稍后在你的 C++ 代码中处理它。

        最新版本的 GCC 允许您从同步信号处理程序中引发异常,这会导致预期的展开和销毁过程。不过,这是非常特定于操作系统和编译器的

        【讨论】:

          【解决方案8】:

          这里有很多答案,但仍然不完整!

          我发现了另一种不执行析构函数的情况。当跨库边界捕获异常时,总是会发生这种情况。

          在此处查看更多详细信息:

          Destructors not executed (no stack unwinding) when exception is thrown

          【讨论】:

          • 这似乎是一个错误,我怀疑 C++ 规范是否允许这种行为。
          • 这里的问题是:“在什么情况下不调用析构函数”。即使这是 Visual Studio 中的错误,它也是问题的有效答案,因为错误也是一种情况。人们从谷歌来到这里,他们中的一些人可能会遇到这个特定的问题,而不知道它是否是错误。
          猜你喜欢
          • 2023-04-04
          • 1970-01-01
          • 2010-11-18
          • 2022-10-15
          • 2012-10-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多