The Life of a Programmer

Mismatched Allocation and Delete Nonsense

Efficient yet confused. Powerful but unsafe. So is the nature of C++ object allocation and instantiation.

(This article is part of the series on Defective C++)

Arrays

The first warning of a memory problem in C++ is the need to match array allocation with special deallocation syntax.

[sourcecode language=”cpp”]
int * a = new int[10];
delete a; //wrong
delete[] a; //right
[/sourcecode]

Any call to `new[]` must be matched with a call to `delete[]`. Since the array in C++ is kind of a dimunitive type, becoming a pointer at any instant, it is not possible for the compiler to enforce this requirement, in which case it’d simply be an inconvenience. Instead you’ll just end up getting undefined run-time behaviour, hopefully your program will crash, but likely you’ll just get unusual memory corruption.

Beyond just a syntax issue, this problem is exemplified by templates: you simply can’t easily write a wrapper that works with both plain pointers and arrays. Look at the shared_ptr wrapper class and you’ll notice it can’t be readily used with an array. This is unfortunate since the pattern of a shared pointer doesn’t change whether the underlying pointer is a single object or an array.

Placement New

An important feature of memory management is the ability to instantiate objects at an already allocated location in memory. This is done with the placement new syntax. Deleting this object however requires an entirely different syntax.

[sourcecode language=”cpp”]
T * a = new (block)T;
a->~T();
[/sourcecode]

Use a custom allocator instead and you’ll also be forced to write  an explicit call to operator delete. While the basic new and delete offer a sane symmetry, the placement syntax is extremely asymmetric and confuses exactly what new and delete are supposed to be doing.

Virtual Destructor

Where the array syntax may be considered annoying, and the placement syntax merely confused, it is hard to deny the ability to delete part of an object is critically flawed. Whenever you delete an object through a pointer to one of its bases, you may either properly delete the whole object, or just delete part of it depending on how it was declared.

[sourcecode language=”cpp”]
struct A { ~A() { } };
struct B : public A { ~B() { } };

B * b = new B;
A * a = b;
delete a; //half-delete
[/sourcecode]

Some might still be shocked to learn the above does not call B::~B but only calls A::~A. Unless the base-class destructor is marked virtual the delete operator will only delete the immediately known type. Here that is an A since it is deleting an A*.

Solutions

The history of these problems stems from C compatibility. In particular, a C-struct uses no more memory than its actual member contents (plus padding for ailgnment). In C, memory allocation and object instantiation are two distinct operations which have to be manually performed. C++  merges these two operations, but does so with some critical flaws..

There is no arguing that at times you’ll need fine control of memory allocation and instantiation. Such options should be provided, but the default should be a sane system where delete just does the right thing. That is, delete A will properly delete an array, single object, or derived object. If the object was allocated via a special allocator it should also properly call the deallocator, or otherwise properly deallocate all via that single delete A syntax.

Doing so may require meta-information to be stored along with any allocated object. Regardless of how an object is created the resulting object must contain enough information to say how it should be deleted. This concept isn’t entirely new, just look at any smart_ptr and you’ll see a deleter function which does exactly this.

Curiously, if you also believe a language should be garbage collected you also implicitly support this feature. The collector will have to know exactly how to destroy any object. Short of a full scanning collector, safe deletion also simplifies the task of any object pool or any variant type for that matter.

Please join me on Discord to discuss, or ping me on Mastadon.

Mismatched Allocation and Delete Nonsense

A Harmony of People. Code That Runs the World. And the Individual Behind the Keyboard.

Mailing List

Signup to my mailing list to get notified of each article I publish.

Recent Posts