4

For example:

int *a, *b;
a = new int[10];
b = new int(12);
b = a; // I know there's memory leak, but let's ignore it first
delete [] b; // line L

What will happen? Will the entire array be deleted successfully?

What if line L is replaced by this: b = a + 1; delete [] b;

Or by this: a++; delete [] a;

And, lastly, if the length of an dynamic array is associated with the starting address, or in other words, associated with the array itself, do we have any way to get the length of it without using another variable to store the length?

Thanks a lot!

Shen Zhuoran
  • 385
  • 3
  • 13
  • The length information is lost, that simple. If you want to keep track, use `std::vector` instead. Internally `new` `delete` do bookkeeping of the address of the first element. – πάντα ῥεῖ Apr 02 '16 at 10:01
  • My g++ signals "free(): invalid pointer" when (b = a + 1; delete [] b;), and delete[] after a++ looks like undefined behaviour, since You will try to access freed memory. – Mateusz Wojtczak Apr 02 '16 at 10:26

3 Answers3

5

The memory block size and array length information is associated with the address of the object, which is the address of the first item of the array.

I.e. your delete[] is safe in the given code

int *a, *b;
a = new int[10];
b = new int(12);
b = a; // I know there's memory leak, but let's ignore it first
delete [] b; // line L

However, there is no portable way to 1access the associated information. It's an implementation detail. Use a std::vector if you need such info.


To understand why the info can't be accessed, note first that memory block size, needed for deallocation, can be larger than the array length times size of array item. So we're dealing here with two separate values. And for an array of POD item type, where item destructor calls are not necessary, there needs not be an explicitly stored array length value.

Even for an array where destructor calls are necessary, there needs not be an explicitly stored array length value associated with array. For example, in code of the pattern p = new int[n]; whatever(); delete[] p;, the compiler can in principle choose to put the array length in some unrelated place where it can easily be accessed by the code generated for the delete expression. E.g. it could be put in a processor register.

Depending on how smart the compiler is, there needs not necessarily be an explicitly stored memory block size either. For example, the compiler can note that in some function f, three arrays a, b and c are allocated, with their sizes known at the first allocation, and all three deallocated at the end. So the compiler can replace the three allocations with a single one, and ditto replace the three deallocations with a single one (this optimization is explicitly permitted by 2C++14 §5.3.4/10).


1 Except, in C++14 and later, in a deallocation function, which is a bit late.
2 C++14 §5.3.4/10: “An implementation is allowed to omit a call to a replaceable global allocation function (18.6.1.1, 18.6.1.2). When it does so, the storage is instead provided by the implementation or provided by extending the allocation of another new-expression. The implementation may extend the allocation of a new-expression e1 to provide storage for a new-expression e2 if …”

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • Thanks a lot! I've got two more questions on your answers. First, can you explain why the block size could be larger than the size of the array? Secondly, did you mean that although the array length is stored somewhere (for a non-POD array) and the "delete" expression can find that info based on the value of the pointer pointing to the array, that info is inaccessible for other codes? – Shen Zhuoran Apr 04 '16 at 03:41
  • 1
    Finding and returning a more perfect match for an allocation request, or dividing a free block to do that, has costs in execution time and memory fragmentation. In Windows the virtual memory functions manage memory efficiently in terms of 4KB blocks called pages. But allocating 4KB when you only need 42 bytes, say, is a bit wasteful! So ordinarily an application uses a higher level API that does extra work to yield lower block **granularity**, reportedly 8 bytes on 32-bit systems and 16 bytes on 64-bit systems. The C runtime can provide an additional layer. Add to this, alignment requirements. – Cheers and hth. - Alf Apr 04 '16 at 08:32
  • 1
    If an array length is stored somewhere as part of evaluating a `new[]` expression, then generally that information is inaccessible via standard portable code, yes. – Cheers and hth. - Alf Apr 04 '16 at 08:41
2
int *a, *b;
a = new int[10];
b = new int(12);
b = a; // I know there's memory leak, but let's ignore it first
delete [] b; // line L

Will the entire array be deleted successfully?

Yes, memory will successfully be freed since the pointer a was set to point to an array of 10 integers

a = new int[10];

then the same memory location address is stored into b

b = a;

therefore the delete[] line correctly deallocates the array chunk

delete [] b;

As you stated the code also leaks the integer variable 12 that was originally allocated and pointed to by b.

As a sidenote calling delete[] to free memory not associated with an array (specifically with a static type mismatch - see [expr.delete]/p3) would have triggered undefined behavior.


Now for some internals on where is the size information stored when allocating memory.

Compilers have some degree of freedom (cfr. §5.3.4/10) when it comes to memory allocation requests (e.g. they can "group" memory allocation requests).

When you call new without a supplied allocator, whether it will call malloc or not, it is implementation defined

[new.delete.single]

Executes a loop: Within the loop, the function first attempts to allocate the requested storage. Whether the attempt involves a call to the Standard C library function malloc is unspecified.

Assuming it calls malloc() (on recent Windows systems it calls the UCRT), attempting to allocate space means doing book-keeping with paged virtual memory, and that's what malloc usually does.

Your size information gets passed along together with other data on how to allocate the memory you requested.

C libraries like glibc take care of alignment, memory guards and allocation of a new page (if necessary) so this might end up in the kernel via a system call.

There is no "standard" way to get that info back from the address only since it's an implementation detail. As many have suggested, if you were to need such an info you could

  • store the size somewhere
  • use sizeof() with an array type
  • even better for C++: use a std::vector<>

That info is furthermore associated with a memory allocation, both of your secondary cases (i.e. substituting the L line with

b = a + 1; delete [] b;

or

a++; delete [] a;

will end in tears since those addresses aren't associated with a valid allocation.

Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • Thanks for your clear answer. As for the size info, it is stored somewhere, but there's no interface to retrieve the value, is that right? – Shen Zhuoran Apr 04 '16 at 03:44
  • 1
    @FrankShen [some functions](http://man7.org/linux/man-pages/man3/malloc_usable_size.3.html) provided by the same CRT might be used, but as I said it's not even guaranteed that `new` uses `malloc`. Plus it will return the size of the block (i.e. might be larger than your requested size due to what I just wrote above). I'd say no reliable and standard way. – Marco A. Apr 04 '16 at 07:36
1

The c++ standard just says that using delete[] on a pointer allocated with new, and using delete on a pointer allocated with new[] is undefined behavior.

So doing this may work, or may not, depending on implementations. It may set your house on fire.

For instance, just suppose that these functions are based on an underlying buffer using malloc() and free().

new and delete will, in most implementations, use a buffer which has exactly the size and address of the item (here an int)

new[] and delete[] are more complex. They must store in the buffer not only size items, but also the value of size. An implementation could store size before the actual items. This would mean that the pointer of the underlying buffer is not the same as the pointer to the first item, which is the value returned by new[]

Mixing array and non array versions would then call free() on invalid pointers, ie pointers that were never returned by malloc. This will crash or trigger an exception

Of course, all this is implementation defined.

galinette
  • 8,896
  • 2
  • 36
  • 87
  • 2
    **−1** “So doing this may work, or may not, depending on implementations. It may set your house on fire.” is just wrong. Did you misunderstand the code? – Cheers and hth. - Alf Apr 02 '16 at 10:26
  • Why is it wrong? Please explain. For me this is UB, and my sentence you quoted is just explaining UB. – galinette Apr 02 '16 at 10:28
  • 4
    In the OP's code, `delete[]` is called on a pointer allocated with `new[]`. Yes, `b` is originally allocated with `new`, but after `b = a` it points to an array allocated with `new[]`. – TonyK Apr 02 '16 at 10:28
  • @TonyK: you're suggesting that the implementation does a lookup on the *value* of `b`, and finds a number which was originally a pointer allocated with `new[]`. Is it more likely that it looks up `b` in the symbol table, and decides that `b` was originally allocated with `new`? – EML Apr 02 '16 at 11:27
  • 1
    @EML: No, because C++ is governed by an international standard that defines the behavior. – Cheers and hth. - Alf Apr 02 '16 at 12:08