operator new[]分配后错误delete操作的UB原因解析
operator new[]与错误delete操作的未定义行为分析 首先看示例代码:
#include <iostream> class Test { public: Test() { std::cout << "Test()\n"; } ~Test() { std::cout << "~Test()\n"; } }; int main() { void* p = operator new[](10 * sizeof(Test)); Test* test = new (p) Test(); test->~Test(); // delete p; // UB1 // delete[] p; // UB2 // delete[] test; // UB3 // operator delete[](test); // UB4? operator delete[](p); std::cout << "End\n"; }
UB2-UB4的未定义行为分析
结合C++20草案N4861的相关条款,逐一拆解:
UB2:delete[] p;
根据N4861 §7.6.2.8 [expr.delete]:
delete[]的操作数必须是指向数组对象首元素的指针,或是空指针。
核心问题有两点:
p是void*,但C++明确规定void不是对象类型,也不存在void[]这种数组类型——标准禁止创建void数组。operator new[]仅分配了原始内存,没有创建任何对象(包括数组对象)。delete[]的语义不只是释放内存,还会自动遍历数组调用每个元素的析构函数。但这里p指向的内存仅手动构造过一个Test且已手动析构,delete[]会错误地将其视为数组,尝试调用不存在的元素析构函数,同时还会去寻找数组分配时才有的元数据(比如元素个数),最终导致未定义行为。
UB3:delete[] test;
根据N4861 §7.6.2.8 [expr.delete]:
delete[]仅用于释放由new[]表达式分配的数组对象。
test指向的是在原始内存中手动构造的单个Test对象,并非new[]分配的数组首元素。delete[]会读取数组分配时存储的元数据(比如内存块开头的元素个数),再调用对应次数的析构函数,但这块内存根本没有该元数据,编译器会乱读数值,错误地调用多次析构函数(包括已手动析构的Test和未构造的内存区域),内存释放逻辑也完全不匹配,属于未定义行为。
UB4:operator delete[](test);
根据N4861 §7.6.2.8 [expr.delete]:释放内存时使用的指针,必须是之前由分配函数(如
operator new[])返回的原始指针,或是通过合法指针转换得到的等价指针。
虽然test和p地址相同,但存在两个关键问题:
operator delete[]的参数类型需匹配分配时的返回类型——分配时返回的是void*,而test是Test*。如果程序存在针对Test*的operator delete[]重载,会调用错误版本直接导致UB。- 从语义上讲,手动构造对象后释放内存,必须使用分配时拿到的原始指针
p,而非指向构造对象的test。标准要求释放动作必须对应原始分配指针,哪怕地址相同,违反规则即为UB。
附带子问题解答
1. 为何delete表达式语法需匹配new分配的对象类型而非new表达式语法?
C++中new/delete的核心是管理对象生命周期,而非单纯的内存分配:
new Test构造1个Test对象,delete需调用1次析构函数再释放内存;new Test[10]构造10个Test对象,delete[]需调用10次析构函数,还要处理数组分配时存储的元数据(如元素个数)。
如果用delete释放new[]的数组,只会调用1次析构函数,剩余对象未析构;用delete[]释放单个对象,会乱读元数据调用多次析构函数。这都是因为对象类型(单个/数组)不匹配,而非new表达式的语法。标准§7.6.2.8明确要求,delete对应单个对象,delete[]对应数组对象,必须匹配分配的对象类型,否则就是UB。
2. 指针p的指向对象类型是什么?p是void*,但void并非对象类型,是否指向void[]且void[]属于对象类型?
p指向的是一块未构造任何对象的原始内存,不存在void[]对象类型——C++标准§6.7.1 [basic.types]明确规定void不是对象类型,也不允许创建void数组。
operator new[]仅负责申请足够大的内存空间,不会在其中构造任何对象,因此p根本不指向任何对象。只有当你用定位new在这块内存中构造Test对象后,test指针才指向一个Test对象,但p本身仍是void*,不指向任何具体的对象类型。
内容的提问来源于stack exchange,提问作者Thomas W.

