C++ 混合运算符new[]和placement new与普通delete[]

C++ 混合运算符new[]和placement new与普通delete[],c++,arrays,initialization,dynamic-memory-allocation,placement-new,C++,Arrays,Initialization,Dynamic Memory Allocation,Placement New,出于好奇,以下内容合法吗 X* p = static_cast<X*>(operator new[](3 * sizeof(X))); new(p + 0) X(); new(p + 1) X(); new(p + 2) X(); delete[] p; // Am I allowed to use delete[] here? Or is it undefined behavior? 从n3242中的5.3.5[expr.delete]开始: 二, [……] 在第二个备选方

出于好奇,以下内容合法吗

X* p = static_cast<X*>(operator new[](3 * sizeof(X)));
new(p + 0) X();
new(p + 1) X();
new(p + 2) X();

delete[] p;   // Am I allowed to use delete[] here? Or is it undefined behavior?

从n3242中的5.3.5[expr.delete]开始:

二,

[……]

在第二个备选方案中(删除 数组)的操作数的值 delete可以是空指针值或 由 上一个数组是新表达式。如果 不是,行为是未定义的。[……]

这意味着对于
delete[]p
p
必须是
new[]p
(一个新表达式)形式或0的结果。鉴于
操作符new
的结果未在此列出,我认为第一个案例是正确的


我相信第二种情况是可以的。 从18.6.1.2[新建.delete.array]:

十一,

void操作符delete[](void*ptr)noexcept

[……]

要求:ptr应为空指针 或其价值应为 由先前对的调用返回 新操作员或 运算符新[](标准::大小,常数 std::nothrow\u t&)未被 被一个介入呼叫无效 操作员删除。[……]

(第3.7.4.2[基本.stc.动态.解除分配]第3段中有类似文本)

因此,只要de/allocation函数匹配(例如,
delete[](新的[3]T)
格式正确),就不会发生任何不好的情况[或者确实如此?见下文]


我想我在5.3.4[expr.new]中跟踪了Jerry警告的规范性文本:

十,

一个新表达式传递 请求分配的空间 函数作为类型的第一个参数 标准:尺寸。这一论点不应成立 小于所选对象的大小 创建;它可能大于 仅创建的对象的大小 如果对象是数组。[……]

在同一段中,下面是一个示例(非常不规范),它强调了实现的新表达式确实可以自由地从分配函数要求比数组占用的空间更多的空间(存储可选的
std::size_t
参数,可供释放函数使用),它们可以抵消到结果中。因此,所有的赌注都在数组的情况下。不过,非数组的情况似乎很好:

auto* p = new T;
// Still icky
p->~T();
operator delete(p);

我敢肯定两人都给了UB

§5.3.4/12表示,新表达式的数组形式可能会给分配的内存量增加一些任意量的开销。然后,数组delete可以/可以使用它期望存在的额外内存执行某些操作,但由于您没有分配它期望的额外空间,因此无法执行。至少,它通常至少会补偿预期分配的额外内存量,以返回到它认为从
运算符new
返回的地址——但由于您尚未分配额外内存或应用偏移量,当它分配到该地址时,它将传递一个指向
运算符delete[]的指针
未从
operator new[]
返回,导致UB(事实上,甚至在返回地址开始之前尝试形成地址在技术上也是UB)

同一部分说,如果它分配了额外的内存,它必须将返回的指针偏移该开销。如果使用从新表达式返回的指针调用
运算符delete[]
而不补偿偏移量,则调用
运算符delete[]
时使用的指针与返回的
运算符new[]
不同,再次给出UB


§5.3.4/12是非规范性注释,但我在规范性文本中没有发现任何与之相矛盾的内容。

我认为这不可能是合法的。因为这意味着这些方程:

new-expression    = allocation-function  +  constructor
delete-expression = destructor  +  deallocation-function
不多不少。但据我所知,标准并没有明确说明这一点。
新表达式
可能比
分配函数+构造函数
一起做的更多。也就是说,实际的方程式可能是这样的,标准并没有明确禁止在任何地方使用:

new-expression    = allocation-function  +  constructor   +  some-other-work
delete-expression = destructor  +  deallocation-function  +  some-other-work

如果他们不是UB,他们应该是。在示例1中,您使用的是
delete[]
,其中底层机制不知道要销毁多少对象。如果
new[]
delete[]
的实现使用cookie,则此操作将失败。示例2中的代码假设地址
q
是要传递给
操作员删除[]
的正确地址,而在使用cookie的实现中则不是这种情况。

正确的地址应该是:

X* p = static_cast<X*>(new char[3 * sizeof(X)]);
// ...
delete[] static_cast<char*>(p);
X*p=static_cast(新字符[3*sizeof(X)]);
// ...
删除[]静态投影(p);

X*p=static_cast(操作符new[](3*sizeof(X));
// ...
操作员删除[](p);
数组删除表达式的类型必须与新表达式完全匹配


第一个例子是UB,因为第5.3.5节(
[expr.delete]
)说

在第一个备选方案(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,并且静态类型应具有虚拟析构函数,或者行为未定义。在第二个备选方案(删除数组)中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义


我的更正版本没有问题,因为(第3.9节
[basic.life]
):

程序可以通过重用对象占用的存储,或者通过显式调用具有非平凡析构函数的类类型的对象的析构函数,来结束任何对象的生存期。对于具有非平凡析构函数的类类型的对象,在重用或释放该对象占用的存储之前,程序不需要显式调用析构函数;但是,如果没有显式调用析构函数,或者如果没有删除表达式(5.3.5)
X* p = static_cast<X*>(new char[3 * sizeof(X)]);
// ...
delete[] static_cast<char*>(p);
X* p = static_cast<X*>(operator new[](3 * sizeof(X)));
// ...
operator delete[](p);