71

What is the right method to delete all the memory allocated here?

  const char* charString = "Hello, World";
  void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
  Buffer* buf = new(mem) Buffer(strlen(charString));

  delete (char*)buf;

OR

  const char* charString = "Hello, World";
  void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
  Buffer* buf = new(mem) Buffer(strlen(charString));

  delete buf;

or are they both same?

Jonathan Callen
  • 11,301
  • 2
  • 23
  • 44
Vink
  • 1,019
  • 2
  • 9
  • 18
  • 8
    Although this is a perfectly valid question, a much easier way to do this would be to just overload `operator new` and `operator delete` inside of `Buffer` to allocate extra space, then to write something like `new (strlen(charString) + 1) Buffer;` – templatetypedef Jul 21 '11 at 23:33

3 Answers3

73

The correct method is:

buf->~Buffer();
::operator delete(mem);

You can only delete with the delete operator what you received from the new operator. If you directly call the operator new function, you must also directly call the operator delete function, and must manually call the destructor as well.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • 1
    Actually should be `::operator delete(mem);` -- while `(void *)buf` will be the same as `mem` on most implementations, its not guarenteed to be the same unless Buffer is a POD type – Chris Dodd Jul 21 '11 at 23:51
  • @bdonlan: if Buffer does not own any resource(heap), does "delete buf" leak memory? The destructor does gets called here, but does it free only "sizeof(buf)" memory? – Vink Jul 22 '11 at 00:51
  • 3
    `delete buf` is undefined behavior. `The delete-expression operator destroys a most derived object (1.8) or array created by a new-expression.`. Since `::operator new` is not a new-expression, you have a constraint violation, and behavior is therefore not defined. – bdonlan Jul 22 '11 at 01:32
  • 2
    But `new(mem) Buffer(strlen(charString))` IS a _new-expression_. – aschepler Jun 02 '13 at 23:59
  • @aschepler: Yes, and see http://stackoverflow.com/questions/4418220/legality-of-using-operator-delete-on-a-pointer-obtained-from-placement-new – Ben Voigt Nov 03 '13 at 01:17
  • 1
    In the linked question, the code was invalid because the implicit `::operator delete` called by the _delete-expression_ involved did not come from `::operator new`. But here the memory did come from `::operator new`. I'm not convinced `delete buf;` is UB here (though the explicit destructor and deallocate is clearly better style). – aschepler Nov 03 '13 at 04:35
  • It is also worth mentioning that if an array is involved (from a new expression involving `operator new[]`), then calling `operator delete[]` directly is simply not possible, and there is no (portable) way to otherwise free the allocated block (unless through a `delete[]` expression). – alecov Aug 23 '16 at 20:38
45

There are two separate notions in C++:

  1. The new/delete operators.

  2. New/Delete expressions.

The operators allocate and deallocate memory. The new expression constructs objects. The delete expression sometimes destroys an object and calls the operator.

Why "sometimes"? Because it depends on the expression. The naked, global new first calls operator-new to allocate memory and then constructs the object; the global delete calls the destructor and deallocates the memory. But all other overloads of new and delete are different:

  • An overloaded new expression calls an overloaded new operator to allocate memory and then proceeds to construct the object.
  • However, there is no such thing as an overloaded delete expression, in particular there is no "placement-delete": Instead, you have to call the destructor manually.

New/Delete operators still have to be overloaded in matching pairs, because the matching delete operator is called when an object constructor throws an exception. However, there is no automatic way to invoke the destructor for an object that has been allocated with an overloaded new operator, so you have to do that yourself.

As the first and most basic example, consider the placement-new operator, which is mandated to take the form void * operator new (size_t, void * p) throw() { return p; }. The matching delete operator is thus mandated to do nothing: void operator delete (void *, void *) throw() { }. Usage:

void * p = ::operator new(5); // allocate only!
T * q = new (p) T();          // construct
q->~T();                      // deconstruct: YOUR responsibility
// delete (p) q;   <-- does not exist!! It would invoke the following line:
::operator delete(p, q);      // does nothing!
::operator delete(q);         // deallocate
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • @thb: I saw your question on this. I'm not sure if you're representing the situation accurately, though :-S – Kerrek SB Jun 30 '13 at 16:21
  • @thb: After double-checking what you mean, I can assure you that that has nothing to do with this question or my answer. – Kerrek SB Jun 30 '13 at 17:41
  • Okay. Thanks. Removing my comments. – thb Jul 02 '13 at 15:41
  • 2
    You're right about the two separate notion, but `::operator new` is called an allocation function in the Standard, not an operator. So bullet #1 should say "The `operator new()` and `operator delete()` allocation and deallocation functions. – Ben Voigt Nov 03 '13 at 01:18
  • 7
    wouldn't your last line be ```::operator delete(p); ``` since this is where the memory was allocated? – Colin Hicks Apr 22 '20 at 02:04
5

The better and saner way, as explained in other answers, is

buf->~Buffer();
::operator delete(mem);

However, assuming there's no such thing as Buffer::operator delete, the delete buf; version is technically okay and will do all the same cleanup. To avoid the Buffer::operator delete possibility, you can say ::delete buf;.

Language-lawyer debate material follows.

5.3.5/1

The delete-expression operator destroys a most derived object (1.8) or array created by a new-expression.

delete-expression:

  • ::opt delete cast-expression
  • ::opt delete [ ] cast-expression

The first alternative is for non-array objects, and the second is for arrays. ...

5.3.5/2

... In the first alternative (delete object), the value of the operand of delete may be a null pointer, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (1.8) representing a base class of such an object (Clause 10). If not, the behavior is undefined.

So the pointer must point at an object created by a new-expression, which is defined:

5.3.4/1

new-expression:

  • ::opt new new-placementopt new-type-id new-initializeropt
  • ::opt new new-placementopt ( type-id ) new-initializeropt

new-placement:

  • ( expression-list )

So a "placement new" does count as a new-expression. Nothing forbidding a delete-expression there.

Also, it turns out the delete-expression does exactly the right things to clean up the object despite the custom creation.

5.3.5/6-9

If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. ...

If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will call a deallocation function (3.7.4.2). Otherwise, it is unspecified whether the deallocation function will be called. [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. - end note]

When the keyword delete in a delete-expression is preceded by the unary :: operator, the global deallocation function is used to deallocate the storage.

So ::delete buf; is entirely equivalent to:

try {
    buf->~Buffer();
} catch(...) {
    ::operator delete(mem);
    throw;
}
::operator delete(mem);
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • Following your reasoning (*placement new* is a *new expression* so we can use *delete*), that would mean you can use `delete buf` even if `mem` was actually allocated with `malloc` or something else ? – zakinster Jun 03 '13 at 06:43
  • @zakinster: Only if the `operator delete` that gets called by the expression is well-defined given the method of allocation. – aschepler Jun 03 '13 at 15:06
  • should not be there also a delete after the attempt to call the destructor ? – vlad_tepesch Jul 20 '20 at 15:15
  • 2
    While this may work in this special case where the buffer has been allocated dynamically and the object's address is not different from `charString`'s address, it only works "accidentally" here and is in general wrong. Calling `::operator delete(mem);` for arbitrary addresses results in disaster. The implementation cannot know it's coming from placement new. – Peter - Reinstate Monica Mar 07 '22 at 10:20