出于好奇,以下是合法的吗?
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?
类似地:
X* q = new X[3]();
(q + 2)->~X();
(q + 1)->~X();
(q + 0)->~X();
operator delete[](q);
答案 0 :(得分:7)
我很确定两者都给了UB。
§5.3.4/ 12表示新表达式的数组形式可能会为分配的内存量增加一些任意数量的开销。数组删除可以/可以使用它期望的额外内存来执行某些操作,但不是因为您没有分配它期望的额外空间。至少它通常至少会补偿它预期分配的额外内存量以回到它认为从operator new
返回的地址 - 但是因为你没有分配额外的内存或者应用了一个偏移量,当它执行时它会传递一个指向operator delete[]
的指针,该指针未从operator new[]
返回,导致UB(事实上,甚至试图在开始之前形成地址)返回的地址在技术上是UB)。
同一部分说如果它分配额外的内存,它必须将返回的指针偏移该开销量。当/如果使用从新表达式返回的指针调用operator delete[]
而不补偿偏移量时,则使用与返回的operator delete[]
不同的指针调用operator new[]
,再次给予UB。
§5.3.4/ 12是一份非规范性的说明,但我没有看到规范性文本中的任何内容与之相矛盾。
答案 1 :(得分:5)
来自n3242的5.3.5 [expr.delete]:
2
[...]
在第二种选择中(删除 array ),操作数的值 delete可以是空指针值或 由a产生的指针值 上一个数组new-expression。如果 不,行为是不确定的。 [...]
这意味着对于delete[] p
,p
必须是某种形式new[] p
(一个新表达式)或0的结果。看作{{1}的结果这里没有列出,我认为第一种情况就出来了。
我相信第二种情况是好的。 从18.6.1.2 [new.delete.array]:
11
operator new
[...]
要求: ptr应为空指针 或其值应为该值 早先的电话回来了 运营商新的或 operator new [](std :: size_t,const std :: nothrow_t&amp;)还没有 通过干预呼叫无效 运营商删除。 [...]
(3.7.4.2 [basic.stc.dynamic.deallocation]第3段中有类似的文字)
因此只要de / allocation函数匹配(例如void operator delete[](void* ptr) noexcept;
格式正确)就不会发生任何不良事件。 [或者是吗?见下文]
我认为我在5.3.4 [expr.new]中跟踪了Jerry警告的规范性文本:
10
new-expression传递的数量 要求分配的空间 作为类型的第一个参数 的std ::为size_t。那个论点应该是没有的 小于物体的大小 创建;它可能大于 仅创建的对象的大小 如果对象是一个数组。 [...]
在同一段落中是一个例子(非规范性),它强调了一个实现的新表达式确实可以自由地从分配函数中询问比数组所占空间更多的内容(存储可选的{{1}可以解释释放函数可用的参数,并且它们可以偏移到结果中。所以所有的赌注都在阵列案例中。非数组的情况似乎很好:
delete[] (new[3] T)
答案 2 :(得分:2)
我认为这不合法。因为这意味着这些方程:
new-expression = allocation-function + constructor
delete-expression = destructor + deallocation-function
没有更多,没有更少。但就我所知,标准确实不确切地说。 new-expression
可能会比allocation-function + constructor
更多地执行此操作。也就是说,实际的方程式可能就是这样,标准并没有明确禁止它:
new-expression = allocation-function + constructor + some-other-work
delete-expression = destructor + deallocation-function + some-other-work
答案 3 :(得分:2)
如果他们不是UB,他们应该是。在示例1中,您使用的是delete[]
,其中底层机制无法确定要销毁多少个对象。如果new[]
和delete[]
的实施使用Cookie,则会失败。示例2中的代码假定地址q
是传递给operator delete[]
的正确地址,而在使用cookie的实现中则不是这样。
答案 4 :(得分:2)
正确的是:
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);
数组删除表达式的类型必须与新表达式完全匹配。
第一个例子是UB,因为第5.3.5节([expr.delete]
)表示
在第一个备选方案(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为动态类型的基类。要删除的对象,静态类型应具有虚拟析构函数或行为未定义。在第二个备选方案( delete array )中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义。
我的更正版本没问题,因为(第3.9节[basic.life]
):
程序可以通过重用对象占用的存储或通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期。对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数;但是,如果没有显式调用析构函数或者如果没有使用delete-expression(5.3.5)来释放存储,则不应该隐式调用析构函数,并且任何程序都依赖于析构函数产生的副作用没有找到 行为。
如果X
有一个非平凡的析构函数,则不允许使用第二个示例,因为(同样是3.9 [basic.life]
):
在对象的生命周期开始之前但在对象将占用的存储之后 在对象的生命周期结束之后和对象占用的存储之前分配38或者 重用或释放任何指向对象所在或位于的存储位置的指针 可以使用但仅限于有限的方式。对于正在建造或销毁的物体,见12.7。除此以外, 这样的指针指的是已分配的存储(3.7.4.2),并且使用指针就像指针类型为
void*
一样, 是很好的定义。可以取消引用这样的指针,但是所得到的左值可以仅在有限的情况下使用 方法,如下所述。如果出现以下情况,程序会有未定义的行为:
- 该对象将是或具有非平凡析构函数的类类型,并且该指针用作delete-expression的操作数,