Given the following code involving operator new[]
and the various wrong versions of 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";
}
live on https://godbolt.org/z/zG3nfzsv6
Could you please explain why UB2 through UB4 are Undefined Behavior, with some layman's term explanation of the standardese of C++20 draft N4861, §7.6.2.8 [expr.delete] and maybe §7.6.2.7 [expr.new]?
Here's my try. I'm quite confident about UB1, but not very confident about UB2 and UB3, even less about UB4.
(Emphasis mine)
§ 7.6.2.8 (1)
delete-expression:
::opt delete cast-expression
::opt delete [ ] cast-expression
The first alternative is a single-object delete expression, and the second is an array delete expression.
§ 7.6.2.8 (2)
In a single-object delete expression, the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (6.7.2) representing a base class of such an object (11.7). If not, the behavior is undefined.
So, for delete
without []
I must not use an array pointer. But I did, so I have UB. Fine.
(Emphasis mine)
In an array delete expression, the value of the operand of delete may be a null pointer value or a pointer value that resulted from a previous array new-expression.74 If not, the behavior is undefined.
Well, I have an array from a previous new expression.
[Note: This means that the syntax of the delete-expression must match the type of the object allocated by new, not the syntax of the new-expression. — end note]
Side question: Why does it mean that?
Okay, so I have operator new[]
and this note tells me, that the syntax of my delete may be different. I do not necessarily need operator delete[]
but something that matches the type of the object.
Side question: What is the type of my object? My pointer p is void*
, but void
is not an object type. Does p point to a void[]
and void[]
is an object type?
§ 7.6.2.8 (11)
For an array delete expression, the deleted object is the array object. When a delete-expression is executed, the selected deallocation function shall be called with the address of the deleted object in a single-object delete expression, or the address of the deleted object suitably adjusted for the array allocation overhead (7.6.2.7) in an array delete expression, as its first argument.
Sorry, I don't understand anything of that. I just have a gut feeling that this might be relevant.
You need to differentiate between the new
/delete
expressions and the new
/delete
operators. They are two different things. The operators simply allocate and free memory. The expressions call the operators, as well as call constructors/destructors on the objects that are being created/destroyed.
In your example, you are allocating raw memory using the new[]
operator directly, but then UB1-UB3 are trying to free that memory using the delete
expression and delete[]
expression, which is wrong.
You need to match things up correctly. Use the delete
/delete[]
expressions only with the new
/new[]
expressions 1. Use the delete
/delete[]
operators only with the new
/new[]
operators.
1 Except in the case of your test
pointer, as you can't call any form of the delete
expression or operator on a pointer that is returned by placement-new
, since placement-new
doesn't allocate new memory, just creates an object inside existing memory. That is why you have to call the object's destructor directly in this situation.