C++未捕获异常不调用析构函数是否属于未定义行为?
问题:未捕获异常时局部对象不调用析构是否属于未定义行为
示例代码
#include <iostream> class Widget { public: ~Widget() { std::cout << "Destructor Called!"; } }; void doStuff() { Widget w; throw 1; } int main() { doStuff(); }
说明:本示例取自Fedor Pikus在2015年CppCon大会的技术分享。
补充:本问题场景和编译器析构函数省略的适用场景有本质差异,析构省略相关的讨论结论不适用于本问询。
解答
结论:该场景不属于未定义行为,属于C++标准明确规定的实现定义行为。
首先明确C++标准中三类容易混淆的行为边界:
- 未定义行为:标准不对程序运行结果做任何约束,编译器可以生成任意逻辑的代码,运行时可能出现崩溃、数据损坏、输出随机结果等任意表现,不存在可预期的行为规律
- 实现定义行为:标准明确列出该场景下的所有合法处理选项,要求编译器厂商必须选择其中一种实现,并且必须在官方文档中明确说明自身选择的处理逻辑
- 未指定行为:标准提供多种合法处理选项,编译器可以自由选择任意一种实现,不需要额外向用户文档化选择结果
回到本问题的场景:
当throw 1;语句抛出异常后,运行时会沿着函数调用栈向上逐层查找匹配的catch处理块,本例中main函数没有设置任何异常捕获逻辑,最终必然会调用std::terminate()终止程序。
对于查找不到异常处理块时、调用std::terminate()之前是否需要执行栈展开(也就是调用抛出点到栈顶所有自动存储期局部对象的析构函数),C++标准明确将该选择权交给编译器实现,且要求实现必须文档化自身的处理规则:
- 部分编译器(如默认配置下的MSVC、部分旧版本GCC)会选择跳过栈展开流程,直接触发
std::terminate(),此时例子中栈上的Widget局部对象w的析构函数不会被调用 - 部分编译器(如默认配置下的GCC、Clang)会先执行完整的栈展开流程,调用所有路径上局部对象的析构函数,之后再触发
std::terminate(),此时析构函数会正常执行输出字符串
两种处理逻辑都是完全符合C++标准要求的合法实现,不存在对错之分。开发者在编写代码时不应该依赖“未捕获异常时局部对象析构函数一定会/一定不会执行”的逻辑实现关键功能,否则代码在不同编译器下会出现表现不一致的问题。
内容的提问来源于stack exchange,提问作者Mark Wallace
相关产品推荐
相关产品推荐

