Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing operator new[] and placement new with ordinary delete[]

Just out of curiosity, is the following legal?

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?

Similarly:

X* q = new X[3]();

(q + 2)->~X();
(q + 1)->~X();
(q + 0)->~X();
operator delete[](q);
like image 560
fredoverflow Avatar asked Jun 27 '11 16:06

fredoverflow


People also ask

What is the difference between delete and delete [] in C Plus Plus?

delete is used for one single pointer and delete[] is used for deleting an array through a pointer.

How does delete [] work in C++?

Delete[] operator is used to deallocate that memory from heap. New operator stores the no of elements which it created in main block so that delete [] can deallocate that memory by using that number.

What happens when we use new and delete operator?

Memory that is dynamically allocated using the new operator can be freed using the delete operator. The delete operator calls the operator delete function, which frees memory back to the available pool. Using the delete operator also causes the class destructor (if one exists) to be called.

Why do we need to use the delete or delete [] syntax to release memory after using the new keyword to allocate memory consider what happens if you don t?

Any time you allocate an array of objects via new (usually with the [ n ] in the new expression), you must use [] in the delete statement. This syntax is necessary because there is no syntactic difference between a pointer to a thing and a pointer to an array of things (something we inherited from C).


2 Answers

From 5.3.5 [expr.delete] in n3242:

2

[...]

In the second alternative (delete array), 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. If not, the behavior is undefined. [...]

which means that for delete[] p, p must have been the result of something of the form new[] p (a new expression), or 0. Seeing as the result of operator new is not listed here, I think the first case is right out.


I believe the second case is Ok. From 18.6.1.2 [new.delete.array]:

11

void operator delete[](void* ptr) noexcept;

[...]

Requires: ptr shall be a null pointer or its value shall be the value returned by an earlier call to operator new or operator new[](std::size_t,const std::nothrow_t&) which has not been invalidated by an intervening call to operator delete. [...]

(there is similar text in 3.7.4.2 [basic.stc.dynamic.deallocation], paragraph 3)

So as long as the de/allocation functions match (e.g. delete[] (new[3] T) is well-formed) nothing bad happens. [ or does it? see below ]


I think I tracked the normative text of what Jerry is warning about, in 5.3.4 [expr.new]:

10

A new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. [...]

Following in the same paragraph is an example (so non-normative) which underlines that the new expressions of an implementation are indeed free to ask more from the allocation function than the space the array takes (storing the optional std::size_t parameter available to deallocation function comes to mind), and that they can offset into the result. So all bets are off in the array case. The non-array case seems fine though:

auto* p = new T;
// Still icky
p->~T();
operator delete(p);
like image 45
Luc Danton Avatar answered Oct 31 '22 08:10

Luc Danton


I'm pretty sure both give UB.

§5.3.4/12 says the array form of a new expression may add some arbitrary amount of overhead to the amount of memory allocated. The array delete can/could then do something with the extra memory it expects to be there, but isn't since you didn't allocate the extra space it expects. At the very least it's normally going to at least compensate for the amount of extra memory it expected to be allocated to get back to the address it believes was returned from operator new -- but since you haven't allocated extra memory or applied an offset, when it does to it'll pass a pointer to operator delete[] that wasn't returned from operator new[], leading to UB (and, in fact, even attempting to form the address before the beginning of the returned address is technically UB).

The same section says that if it allocates extra memory, it has to offset the returned pointer by the amount of that overhead. When/if you call operator delete[] with the pointer that was returned from the new expression without compensating for the offset, you're calling operator delete[] with a pointer that's different from the one operator new[] returned, giving UB again.

§5.3.4/12 is a non-normative note, but I don't see anything in the normative text to contradict it.

like image 103
Jerry Coffin Avatar answered Oct 31 '22 07:10

Jerry Coffin