Legality of using operator delete on a pointer obtained from placement new

I'm dang certain that this code ought to be illegal, as it clearly won't work, but it seems to be allowed by the C++0x FCD.

class X { /* ... */};
void* raw = malloc(sizeof (X));
X* p = new (raw) X(); // according to the standard, the RHS is a placement-new expression
::operator delete(p); // definitely wrong, per litb's answer
delete p; // legal?  I hope not

Maybe one of you language lawyers can explain how the standard forbids this.

There's also an array form:

class X { /* ... */};
void* raw = malloc(sizeof (X));
X* p = new (raw) X[1]; // according to the standard, the RHS is a placement-new expression
::operator delete[](p); // definitely wrong, per litb's answer
delete [] p; // legal?  I hope not

This is the closest question I was able to find.

EDIT: I'm just not buying the argument that the standard's language restricting arguments to function void ::operator delete(void*) apply in any meaningful way to the operand of delete in a delete-expression. At best, the connection between the two is extremely tenuous, and a number of expressions are allowed as operands to delete which are not valid to pass to void ::operator delete(void*). For example:

struct A
{
  virtual ~A() {}
};

struct B1 : virtual A {};

struct B2 : virtual A {};

struct B3 : virtual A {};

struct D : virtual B1, virtual B2, virtual B3 {};

struct E : virtual B3, virtual D {};

int main( void )
{
  B3* p = new E();
  void* raw = malloc(sizeof (D));
  B3* p2 = new (raw) D();

  ::operator delete(p); // definitely UB
  delete p; // definitely legal

  ::operator delete(p2); // definitely UB
  delete p2; // ???

  return 0;
}

I hope this shows that whether a pointer may be passed to void operator delete(void*) has no bearing on whether that same pointer may be used as the operand of delete.


Solution 1:

The Standard rules at [basic.stc.dynamic.deallocation]p3

Otherwise, the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(size_t) or operator new(size_t, const std::nothrow_t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](size_t) or operator new[](size_t, const std::nothrow_t&) in the standard library.

Your delete call will call the libraries' operator delete(void*), unless you have overwritten it. Since you haven't said anything about that, I will assume you haven't.

The "shall" above really should be something like "behavior is undefined if not" so it's not mistaken as being a diagnosable rule, which it isn't by [lib.res.on.arguments]p1. This was corrected by n3225 so it can't be mistaken anymore.

Solution 2:

The compiler doesn't really care that p comes from a placement new call, so it won't prevent you from issuing delete on the object. In that way, your example can be considered "legal".

That won't work though, since "placement delete" operators cannot be called explicitly. They're only called implicitly if the constructor throws, so the destructor can run.

Solution 3:

I suppose you might get away with it (on a particular compiler) if

  1. new/delete are implemented in terms of malloc/free and
  2. the placement new actually uses the same mechanism for keeping track of the destructor associated with allocations as the standard new, so that the call to delete could find the right destructor.

But there is no way it can be portable or safe.