2

This question was asked as part of Does delete[] deallocate memory in one shot after invoking destructors? but moved out as a separate question.

It seems (Correct me if wrong) that the only difference between delete and delete[] is that delete[] will get the array size information and invoke destructors on all of them, while delete will destruct the only first one. In particular, delete also has access to the info on how much total memory is allocated by new[].

If one doesn't care about destructing the dynamically allocated array elements, and only care that the memory allocated either by new or new[] be deallocated, delete seems to be able to do the same job.

This How does delete[] "know" the size of the operand array? question's accepted answer has one comment from @AnT and I quote

Also note that the array element counter is only needed for types with non-trivial destructor. For types with trivial destructor the counter is not stored by new[] and, of course, not retrieved by delete[]

This comment suggests that in general delete expression knows the amount of the entire memory allocated and therefore knows how much memory to deallocate in one shot in the end, even if the memory hold an array of elements. So if one writes

auto pi = new int[10];
...
delete pi;

Even though the standard deems this as UB, on most implementations, this should not leak memory (albeit it is not portable), right?

Community
  • 1
  • 1
Rich
  • 1,669
  • 2
  • 19
  • 32
  • 1
    Why has this moved to a separate question? – Ed Heal Sep 10 '15 at 18:17
  • Leaking memory is different than memory fragmentation. You can have no memory available due to fragmentation and still have no memory leaks. – Thomas Matthews Sep 10 '15 at 18:18
  • Even if it was capable of deallocating the memory why would you want to not destroy the objects? Why would the standard committee care about such behavior enough to define it? – imreal Sep 10 '15 at 18:19
  • @EdHeal This is particular to `delete`. As suggested by @black in the linked question, I should move **separate** questions out. – Rich Sep 10 '15 at 18:20
  • It is essentially the same question. You make the assumption that `delete` and `delete[]` will "reallocated" memory as if both operators know the size of the memory to reallocate in the same manner. Could it be possible that they are implemented in totally different ways. Also why not fix the code. – Ed Heal Sep 10 '15 at 18:23
  • @imreal one very inappropriate example: you don't want to handle destructors emitting exceptions. You just want to deallocate the memory... – Rich Sep 10 '15 at 18:28
  • @AlanStokes As I understand, if exception from a destructor is not handled, stack unwinding will take place and since I am just using a plain pointer, the underlying memory won't be deallocated. Of course, the `delete` on the first element could throw but the chance of hitting an exception is low then. – Rich Sep 10 '15 at 18:36
  • What I meant was, given that `delete [] pi` is well-defined and `delete pi` is UB, what reason do you have for wanting to use the latter? Destroying an `int` will never throw, since it doesn't do anything. – Alan Stokes Sep 10 '15 at 18:38
  • @rcih - Just write good code - and avoid these pitfalls. When playing with fire people tend to end up in A&E – Ed Heal Sep 10 '15 at 18:39
  • Have I heard it right? Are you trying to protect yourself from exceptions thrown from destructors by not calling destructors? – n. m. could be an AI Sep 10 '15 at 19:00
  • @n.m. I know it is crazy. I just want to give an example but couldn't think of any real use case. That was just some wild example... – Rich Sep 10 '15 at 19:03
  • A motivating example to use `delete` where the language requires `delete[]`? There's none. – n. m. could be an AI Sep 10 '15 at 19:20

3 Answers3

3

Under the C++ standard, calling delete on something allocated with new[] is simply undefined behavior, as is calling delete[] on something allocated with new.

In practice, new[] will allocate the memory through something like malloc as will new. delete will destroy the pointed-to object, then send the memory to something like free. delete[] will destroy all of the objects in the array, then send the memory to something like free. Some extra memory may be allocated by new[] to pass to delete[] to give delete[] the number of elements to be destroyed, or not.

If actual malloc/free is used, then some implementations will allow a pointer to anywhere in the malloc'd block to be used. Others won't. The exact same value is required to be passed to free as you got from malloc for this to be defined. There is an issue here in that if new[] malloced some extra room for the array size/element stride and stuck it before the block, then delete is passed the pointer-to-the-first element, then delete will pass free a different pointer than new[] got from malloc. (I think there is an architecture where something like this happens.)

Like most undefined behavior, you can no longer rely on auditing the code you write, but instead you are now committed to auditing both the produced assembly, and the C/C++ standard libraries you interact with, before you can determine if the behavior you want to do is correct. In practice, that is a burden that will not be fulfilled, so your code ends up having negative value, even if you check that things work the way you expect the one time you actually checked. How will you ensure that an identical check (of the resulting binary and its behavior) will occur every time the compiler version, standard library version, OS version, system libraries, or compiler is changed?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Can you cite where passing a pointer to the middle of the block to `free` is well-defined, in either C or C++? – Alan Stokes Sep 10 '15 at 18:40
  • "a pointer to anywhere within the free block is a valid argument to free" — nope. – n. m. could be an AI Sep 10 '15 at 18:46
  • @n.m. huh, that is right. Fixed. I remember that working, but I should not have claimed it was ok under the standard without checking. My bad. – Yakk - Adam Nevraumont Sep 10 '15 at 18:50
  • 1
    @n.m. To quote the C standard: "If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined." – James Kanze Sep 10 '15 at 18:50
  • @JamesKanze I said something stupid in an earlier edit of the above post, n.m. was quoting me. – Yakk - Adam Nevraumont Sep 10 '15 at 18:51
0

This is correct. Difference between delete and delete[] is that the latter knows the number of items allocated in the array and calls destructor on every object on them. To be 100% correct, both actually 'know' it - the number of items allocated for an array is equal to the allocated memory size (which both know) divided by the size of the object.

One might ask, why do we need delete[] and delete than - why can't delete perform the same calculations? The answer is polymorphism. The size of the allocated memory will not be equal to the sizeof static objec when deletion is done through the pointer to the base class.

On the other hand, delete[] does not take into account a possibility of object being polymorphed, and this is why dynamic arrays should never be treated as polymorphic objects (i.e. allocated and stored as a pointer to the base class).

As for leaking memory, delete will not leak memory in case of POD types when used on arrays.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Of course, all that info above is implementation specific - but correct for any real implemenation I have seen. – SergeyA Sep 10 '15 at 18:35
  • 1
    Strange. It doesn't correspond to any of the implementations I know of. – James Kanze Sep 10 '15 at 18:42
  • I am quite confused about the interaction between `delete` and polymorphism. Are you saying `delete` doesn't know what the dynamic type is of the element pointer is pointing at? – Rich Sep 10 '15 at 18:46
  • Me too, I think [it's not correct](http://coliru.stacked-crooked.com/a/b691a0299f4f98b9) – alain Sep 10 '15 at 18:47
  • @Rich, you misunderstood my point. Of course, delete does not know dynamic type. This is the whole reason why destructor has to be made virtual on polymorphic types! All delete is doing is calling the destructor, through virtual table for virtual destructors, staticaly for non-virtual ones. – SergeyA Sep 10 '15 at 20:02
  • @alain, your example is not correct. What you've shown is that delete[] called virtual destructors - which indeed it did. To see the program dumping core spectacularly, use two classess with different sizes! – SergeyA Sep 10 '15 at 20:03
  • Oh, I see, [you are right.](http://coliru.stacked-crooked.com/a/43c1bb2125638e64) Thanks! – alain Sep 10 '15 at 21:32
  • @JamesKanze, what are the implemenations you know of and where they differ from my explanation? – SergeyA Sep 10 '15 at 21:53
0

A concrete reason to avoid all constructs provoking undefined behavior, even if you cannot see how they could possibly go wrong, is that the compiler is entitled to assume that undefined behavior never happens. For instance, given this program...

#include <iostream>
#include <cstring>
int main(int argc, char **argv)
{
    if (argc > 0) {
        size_t *x = new size_t[argc];
        for (int i = 0; i < argc; i++)
            x[i] = std::strlen(argv[i]);
        std::cout << x[0] << '\n';
        delete x;
    }
    return 0;
}

... the compiler might emit the same machine code as it would for ...

int main(void) { return 0; }

... because the undefined behavior on the argc > 0 control path means the compiler may assume that path is never taken.

zwol
  • 135,547
  • 38
  • 252
  • 361