Why Is It Undefined Behavior to Delete[] an Array of Derived Objects via a Base Pointer

Why is it undefined behavior to delete[] an array of derived objects via a base pointer?

Base* p = new Base[n] creates an n-sized array of Base elements, of which p then points to the first element. Base* p = new Derived[n] however, creates an n-sized array of Derived elements. p then points to the Base subobject of the first element. p does not however refer to the first element of the array, which is what a valid delete[] p expression requires.

Of course it would be possible to mandate (and then implement) that delete [] p Does The Right Thing™ in this case. But what would it take? An implementation would have to take care to somehow retrieve the element type of the array, and then morally dynamic_cast p to this type. Then it's a matter of doing a plain delete[] like we already do.

The problem with that is that this would be needed every time an array of polymorphic element type, regardless of whether the polymorphism is used on not. In my opinion, this doesn't fit with the C++ philosophy of not paying for what you don't use. But worse: a polymorphic-enabled delete[] p is simply useless because p is almost useless in your question. p is a pointer to a subobject of an element and no more; it's otherwise completely unrelated to the array. You certainly can't do p[i] (for i > 0) with it. So it's not unreasonable that delete[] p doesn't work.

To sum up:

  • arrays already have plenty of legitimate uses. By not allowing arrays to behave polymorphically (either as a whole or only for delete[]) this means that arrays with a polymorphic element type are not penalized for those legitimate uses, which is in line with the philosophy of C++.

  • if on the other hand an array with polymorphic behaviour is needed, it's possible to implement one in terms of what we have already.

Segmentation fault after delete[] on base class pointer

Simply put, you have undefined behavior. You aren't providing delete[] with a pointer you got from new[]. You may think you do, but for the pointers to be the same in the array version, their static type has to match. You converted the pointer into a pointer to a base class.

Practically, when you don't have that added float, your implementation probably maintains sizeof(B) == sizeof(A). So the destructor and deallocation function invocations don't do anything immediately harmful. But it's just as undefined.

How is memory deallocated by delete operator with pointer to base class

Your code has undefined behavior.

It's ok to use

base* ptr = new derived();
delete ptr;

but it is not ok to use

base* ptr = new derived[10];
delete [] ptr;

Here's the relevant text from the C++11 Standard (emphasis mine):

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

Base pointer to array of derived objects

If you look at the expression p[1], p is a Base* (Base is a completely-defined type) and 1 is an int, so according to ISO/IEC 14882:2003 5.2.1 [expr.sub] this expression is valid and identical to *((p)+(1)).

From 5.7 [expr.add] / 5, when an integer is added to a pointer, the result is only well defined when the pointer points to an element of an array object and the result of the pointer arithmetic also points the an element of that array object or one past the end of the array. p, however, does not point to an element of an array object, it points at the base class sub-object of a Derived object. It is the Derived object that is an array member, not the Base sub-object.

Note that under 5.7 / 4, for the purposes of the addition operator, the Base sub-object can be treated as an array of size one, so technically you can form the address p + 1, but as a "one past the last element" pointer, it doesn't point at a Base object and attempting to read from or write to it will cause undefined behavior.

Is it undefined behavior to deallocate a pointer returned by a global replacement operator new, without calling a replacement operator delete? (C++17)

Both examples are undefined behavior. Now that I've taken the time to look through the C++17 standard final draft, I've found the evidence I need.


Example A

With regards to operator new:

Allocation functions - § 6.7.4.1.2

If the request succeeds, the value returned shall be a
non-null pointer value (7.11) p0 different from any previously returned value p1, unless that value p1 was
subsequently passed to an operator delete

In example A we call a new-expression, Simple* a = new Simple(), which internally will call the appropriate operator new. We bypass operator delete when we call UserImplementedFree(static_cast<void*>(a)). Even though operator delete would invoke this function, and presumably do the same deallocation, the catch is that any subsequent calls to operator new can now potentially return a pointer that matches the address that a had. And a was never passed to operator delete. So we've broken the rule stated above.


Example B

delete-expression - § 8.3.5.2

...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 (4.5)
representing a base class of such an object (Clause 13). If not, the behavior is undefined. 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.
83 If not, the behavior is undefined.

In example B, we do not allocate addr through a new-expression. And then we attempt to use a delete-expression to deallocate it. Which violates the rule above.


What would defined behavior look like?

The main feature of these examples is the separation of construction from allocation, and separation of destruction from deallocation. The standard states the following:

new-expression - § 8.3.4.11

For arrays of char, unsigned char,
and std::byte, the difference between the result of the new-expression and the address returned by the
allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.11) of
any object type whose size is no greater than the size of the array being created. [ Note: Because allocation
functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with
fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating
character arrays into which objects of other types will later be placed
. — end note ]

So defined behavior could potentially look like this:

{
//Allocates bytes
char* bytes = new char[sizeof(Simple)];
//Invokes constructor
Simple* a = new ((void *)bytes) Simple();
//Invokes destructor
a->~Simple();
//Deallocates
delete[] bytes;
}

Once again, not necessarily good practice, but defined behaviour.

Possible bug on delete[] for all g++ versions or not defined behaviour for this?

I 'm doing something wrong? or its a g++ bug?

The behavior of your program is undefined:

If the static type of the object that is being deleted differs from
its dynamic type (such as when deleting a polymorphic object through a
pointer to base), and if the destructor in the static type is virtual,
the single object form of delete begins lookup of the deallocation
function's name starting from the point of definition of the final
overrider of its virtual destructor. Regardless of which deallocation
function would be executed at run time, the statically visible version
of operator delete must be accessible in order to compile. In other
cases, when deleting an array through a pointer to base, or when
deleting through pointer to base with non-virtual destructor, the
behavior is undefined
.

Why does polymorphism not apply on arrays in C++?

You get undefined behavior because operator delete[] has no idea what kind of object is stored in the array, so it trusts the static type to decide on offsets of individual objects. The standard says the following:

In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

The array's static type needs to match the element type that you use for the allocation:

Derived* p = new Derived[4]; // Obviously, this works

This Q&A goes into more details on the reason why the standard has this requirement.

As far as fixing this behavior, you need to create an array of pointers, regular or smart (preferably smart to simplify memory management).

Deleting a derived object via a pointer to its base class

It won't leak the object you are deleting, its memory block will be freed.

If you have not declared the destructor in base_class to be virtual then it will leak any dynamically allocated objects contained within derived_class that rely on the destructor of derived_class being called to free them. This is because if the destructor is not virtual, the derived_class destructor is not called in this case. It also means that destructors of "embedded objects" within derived_class will not automatically be called, a seperate but additional problem, which can lead to further leaks and the non-execution of vital cleanup code.

In short, declare the destructor in base_class to be virtual and you can safely use the technique you have presented.

For a coded example, see:

In what kind of situation, c++ destructor will not be called?

Is it undefined behaviour to delete a null void* pointer?

I wonder how you can reach up a situation where you are deleting a pointer only if it is null. But staying in language lawyering mode...

In C++ 03

5.3.5/1

the operand of delete shall have a pointer type or a class type having a single conversion to a pointer type.

void* is a pointer type so a null void pointer meets the static requirement.

5.3.5/2

In either alternative [delete and delete[]], if the value of the operand of delete is the null pointer the operation has no effect.

And this gives the wanted behavior.

5.3.5/3

In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor or the behavior is undefined.

This is not relevant, a null pointer doesn't reference an object on which to check the additional constraint.

In C++ 0X

5.3.5/1

The operand shall have a pointer to object type, or a class type having a single non-explicit conversion function (12.3.2) to a pointer to object type.

void* isn't a pointer to object type, so should be rejected.

What happens when delete a polymorphic object without a virtual destructor?

What happens when b is deleted without a virtual destructor?

We don't know. The behavior is undefined. For most actual cases the destructor of Derived might no be invoked, but nothing is guaranteed.

5.3.5 Delete
[expr.delete]

(emphasis mine)

In the first alternative (delete object), if the static type of the
object to be deleted is different from its dynamic type, the static
type shall be a base class of the dynamic type of the object to be
deleted and the static type shall have a virtual destructor or the
behavior is undefined
.



Related Topics



Leave a reply



Submit