析构函数抛异常时多次调用delete的C++标准合规性及跨平台问题
析构函数抛异常后重复delete的未定义行为问题
现象描述
- Visual Studio编译运行:当析构函数抛出异常时,
operator delete不会被调用;析构函数不抛异常时则会调用,代码可正常执行。 - GCC编译运行:类包含
std::string成员时触发段错误;移除std::string成员时,程序因free(): double free detected in tcache 2终止,说明此时即使析构函数抛异常,operator delete仍会被调用。
测试代码
#include <string> #include <iostream> class MyClass { public: void *operator new(size_t size) { void *p = std::malloc(size); if (!p) throw std::bad_alloc(); return p; } void operator delete(void *p) { std::free(p); } ~MyClass() noexcept (false) { if (s == "123") throw int{}; } std::string s{"123"}; }; int main() { MyClass* myobject = new MyClass{}; try { delete myobject; } catch (...) {} std::cout << "<" << myobject->s << ">"; myobject->s = "abc"; delete myobject; return 0; }
问题解答
1. 是否属于C++标准定义的未定义行为?
是,这段代码的行为完全属于未定义行为。
C++标准并未明确规定:当delete表达式触发的析构函数抛出异常时,对应的operator delete是否会被调用。不同编译器可以根据自身实现选择调用或不调用,这直接导致了你看到的跨平台行为差异。
2. 首次delete抛异常后能否再次调用delete?
绝对不能。
- 如果首次
delete已经调用了operator delete(如GCC无std::string成员的场景),第二次delete就是对已释放内存的重复释放,必然触发内存错误。 - 如果首次
delete未调用operator delete(如VS的场景),此时对象已经经历了部分析构(比如std::string成员可能已经被销毁),对象处于无效状态。后续访问myobject->s以及再次delete的操作,本质是访问已销毁对象的内存,同样属于未定义行为——VS环境下没崩溃只是巧合,并非代码合法。
你的代码依赖了标准未规定的编译器实现细节,同时存在操作无效对象/重复释放内存的问题,因此不可能跨平台正常运行。
内容的提问来源于stack exchange,提问作者Rasim Valiullin
相关产品推荐
相关产品推荐

