You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 12:33:06