为什么在异常情况下析构函数不会被调用?

66

我本来期望在这个程序中 A::~A() 会被调用,但现在没有被调用:

#include <iostream>

struct A {
  ~A() { std::cout << "~A()" << std::endl; }
};

void f() {
  A a;
  throw "spam";
}

int main() { f(); }
然而,如果我把最后一行改成

int main() try { f(); } catch (...) { throw; }

然后调用A::~A()

我正在使用来自Visual Studio 2005的"Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86"进行编译。命令行是cl /EHa my.cpp

编译器像往常一样正确吗?标准在这个问题上是怎么说的?


1
仅供参考,我在Visual C++ 2003中使用相同的代码重现了这个问题。支持该问题。 - paercebal
6个回答

83
析构函数未被调用是因为在堆栈展开之前,未处理的异常调用了terminate()。
具体的C++规范细节超出了我的知识范围,但使用gdb和g++进行调试跟踪似乎证实了这一点。
根据草案标准第15.3条第9款:
9. 如果程序中没有找到匹配的处理程序,则调用函数terminate()(_except.terminate_)。在调用terminate()之前是否展开堆栈是由实现定义的。

2
赞成引用官方标准文档。 - comfreak

20

C++语言规范指出: 在从try块到throw表达式的路径上构造的自动对象调用析构函数的过程称为“堆栈展开”。 您原始的代码中没有包含try块,因此堆栈展开不会发生。


1
所选答案提到了terminate函数,这是最终原因。然而,我认为这个答案更接近正确的答案。如果不使用try,似乎堆栈展开不会发生,这就是析构函数不会被调用的原因。我的实验似乎表明,在正确的错误被捕获之前,析构函数会被调用,而不是在catch后面的代码块运行时才调用。 - Zheng Liu

3

抱歉,我手头没有标准的副本。

我想要一个明确的答案,所以有人能分享一下具体情况吗:

据我了解,只有在以下情况下才会调用terminate():

  • 异常处理机制找不到抛出异常的处理程序。
    以下是更具体的情况:
    • 在堆栈展开期间,异常逃脱了析构函数。
    • 抛出表达式时,异常逃脱了构造函数。
    • 异常逃脱了非局部静态(即全局)对象的构造函数/析构函数。
    • 异常逃脱了使用atexit()注册的函数。
    • 异常逃脱了main()。
  • 当当前没有异常正在传播时,尝试重新抛出异常。
  • 未预期的异常通过异常规范从带有异常规范的函数中逃逸(通过unexpected)。

2

这个问题很容易在Google上找到答案,但我在这里分享一下我的情况。

请确保您的异常不会越过extern "C"边界或使用MSVC选项/EHs(启用C++异常 = 是,带有Extern C函数 (/EHs))


2
在第二个示例中,当它离开try{}块时,dtor被调用。
在第一个示例中,在离开main()函数后,程序关闭时调用dtor --- 此时cout可能已经被销毁。

1
不,这是错误的。析构函数保证在程序离开 main 之前被调用。cout 析构函数保证在此之后被调用。 - Konrad Rudolph
1
更正:在离开f之前必须调用a的析构函数! - Konrad Rudolph
2
此外,第一个示例中程序永远不会离开main函数,而是通过“未预期异常”终止。 - ejgottl
@ejgottl:这个例子不符合通过terminate()或unexpected()终止的任何条件。unexpected()需要一个throw规范,而terminate()需要在dtor中有一个异常。 - James Curran
@ejgotti:无论如何,即使调用了terminate(),当它关闭时,cout也会停止工作,这就是我的观点。 - James Curran
@James:在离开函数f()之前,必须调用所有局部对象的析构函数,无论它是如何离开的(返回/异常)。否则,RAII将无法工作。仍在研究为什么会调用terminate(),因为它永远不会返回,所以没有堆栈展开。 - Martin York

2
我也认为编译器不会生成与“a”相关的代码,因为它没有被引用,但这不是正确的行为,因为析构函数执行了必须执行的操作。
所以,我在VS2008/vc9(+SP1)中尝试了Debug和Release,并且在抛出异常后调用了~A,从f()中退出 - 如果我没错的话,这是正确的行为。
现在我刚刚在VS2005/vc8(+SP1)中尝试了一下,结果是相同的行为。
我使用断点来确保。我刚刚检查了控制台,也有“~A”消息。也许你在其他地方做错了?

1
我创建了一个文本文件,其中包含第一个示例(不使用try),打开“Visual Studio 2005命令提示符”并使用cl /EHa my.cpp编译文件。 运行结果: 此应用程序已请求运行时以异常方式终止。 请联系应用程序的支持团队以获取更多信息。 - Constantin
1
我不熟悉命令行中的编译参数,但我猜想它与发布模式类似,如果没有try/catch语句,代码将无法通过throw指令(这是我尝试时发生的情况),应用程序将会“崩溃”(这是期望的行为)。 - Klaim
1
经过查看汇编清单,我相信优化默认是关闭的。所以更接近于调试模式。顺便说一下,请注意paercebal的评论,他/她已经成功在VS2003中复现了这个问题。 - Constantin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接