Search code examples
c++arrayslanguage-lawyerplacement-new

Which sort of UB do I have here, deleting array of operator new[]?


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.

UB1

(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.

UB2 to UB4

(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.


Solution

  • 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.