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

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[]的操作数必须是指向数组对象首元素的指针,或是空指针。

核心问题有两点:

  1. p是void*,但C++明确规定void不是对象类型,也不存在void[]这种数组类型——标准禁止创建void数组。
  2. 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地址相同,但存在两个关键问题:

  1. operator delete[]的参数类型需匹配分配时的返回类型——分配时返回的是void*,而test是Test*。如果程序存在针对Test*的operator delete[]重载,会调用错误版本直接导致UB。
  2. 从语义上讲,手动构造对象后释放内存,必须使用分配时拿到的原始指针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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 14:47:37