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

C++23改动后,动态unsigned char数组作存储的delete合法性及解读

C++中unsigned char数组作存储的delete合法性问题

先看基础示例代码:

unsigned char* storage = new unsigned char[sizeof(T)];
T* foo = new(storage) T; // 第2行
// 使用*foo
foo->~T(); // 销毁foo

delete[] storage; // C++23中是否属于未定义行为?还有其他做法吗?

问题背景

第2行执行后,数组本身仍处于存活状态(它是T的存储载体,T嵌套在数组的存储范围内),但数组的每个unsigned char元素此时已结束生命周期——因为它们的存储被T的对象复用,且T并非嵌套在单个数组元素中,而是占用了整个数组的存储空间。

核心疑问是:使用完毕后,通过指向已结束生命周期的第一个数组元素的指针storage来删除数组,这种操作在C++23中是否合法?

标准条款的变化

  • C++23之前:[basic.life]/6条款仅禁止以下情况的delete操作:

    程序会产生未定义行为,如果:
    (6.1) 对象是或将是带有非平凡析构函数的类类型,且该指针被用作delete表达式的操作数
    由于unsigned char不是类类型,所以这条规则不适用,delete[] storage是合法的。

  • C++23及之后:该条款被修改为:

    程序会产生未定义行为,如果:(6.1) 该指针被用作delete表达式的操作数
    这似乎把unsigned char也纳入了禁止范围,那是不是这种用法就不能用了?

三种解读的正确性分析

我们逐一分析你提出的三种解读:

解读1:指针指向T对象表示的unsigned char,仍存活

这种说法不成立。T的对象表示是对其存储内容的解释,并非实际存在的unsigned char对象。当T在数组存储中构造时,原数组的每个unsigned char元素的生命周期就已结束——存储被复用,原对象不复存在,不存在所谓“属于对象表示的新unsigned char”。

解读2:[basic.life]/6不适用于此场景,因为复用已结束

这是正确的解读。[basic.life]/6条款的适用前提是“在对象占用的存储被复用之前”使用指向已死对象的指针。而在这个场景中,我们是在T被析构、存储复用结束后才调用delete[]。此时数组对象本身的生命周期并未结束(它的存储只是被临时用来容纳T,没有被销毁或重新分配),delete[]操作的是数组对象本身,而非已死的数组元素——只要指针storage仍指向数组的起始地址,这个delete操作就是合法的。

解读3:因无法指定对齐,这种用法从一开始就不被允许

这是错误的。对齐问题是独立的另一个问题:new unsigned char[sizeof(T)]只保证unsigned char的对齐要求,不一定满足T的对齐需求,确实可能导致未定义行为,但这和delete阶段的生命周期问题无关。即便用alignas(T)解决对齐问题,原delete合法性的疑问依然存在。

更稳妥的替代写法

为了彻底规避数组元素生命周期带来的歧义,建议直接用operator new/operator delete分配和释放原始存储,同时显式指定对齐要求:

void* storage = operator new(sizeof(T), align_val_t(alignof(T)));
T* foo = new(storage) T;
// 使用foo
foo->~T();
operator delete(storage);

内容的提问来源于stack exchange,提问作者JMC

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 18:53:08