The same form that was used to create an object should always be used to delete it. Specifically, deallocation should correspond to allocation as
per the table below.
Table 1. Matching allocation and deallocation ways
Allocation |
Deallocation |
p = new T();
|
delete p;
|
p = new T[5];
|
delete[] p;
|
p = malloc(sizeof(int)*5);
|
free(p);
|
What is the potential impact?
Using a mismatching deallocation construct leads to undefined behavior. This means the compiler is not bound by the language standard anymore and
your program has no meaning assigned to it.
Practically, you can observe the following effects:
- Deleting a single object with
delete[]
leads to a segmentation fault trying to access memory-manager metadata that is not there.
- Deleting an array with
delete
leads to a memory leak because it will delete and deallocate only the first element of the array.
- Freeing with
free()
the underlying memory for an object constructed with new
will skip the destructor call, most
likely leading to a memory leak. Additionally, a destructor might still be called on deallocated memory causing further undefined behavior.
Why is the issue raised for a type with a trivial destructor?
Automatic constructor and destructor invocation is not the only difference between the C-style malloc
/free
memory
allocator, and the C++-style new
/delete
.
These two memory allocators use different metadata and different algorithms. For example, new
has an array form new[]
that stores an "array cookie".
The following example causes undefined behavior, even though the destructor has now effect, because free()
expects different metadata
for the pointer it is passed than what is arranged by the new
operator:
struct TrivialClass {};
TrivialClass* p = new TrivialClass();
free(p); // Noncompliant: no-op destructor is skipped; still undefined behavior
In the code below, delete[]
expects to find an array cookie and fails:
int* p = malloc(10 * sizeof(int));
delete[] p; // Noncompliant: expect array cookie
If you need allocate memory in a custom T::operator new(std::size_t)
, you should use void* ::operator new(std::size_t)
and not free()
.
Note that ::operator new
is still not compatible with free()
:
auto p = ::operator new(10 * sizeof(int));
free(p); // Noncompliant