Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++, is the length information of a dynamic array associated with the pointer or the address of the first element?

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!

like image 608
Shen Zhuoran Avatar asked Feb 08 '23 08:02

Shen Zhuoran


2 Answers

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 …”

like image 200
Cheers and hth. - Alf Avatar answered Feb 09 '23 20:02

Cheers and hth. - Alf


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.

like image 30
Marco A. Avatar answered Feb 09 '23 21:02

Marco A.