How does delete[] know it's an array?
Alright, I think we all agree that what happens with the following code is undefined, depending on what is passed,
void deleteForMe(int* pointer)
{
delete[] pointer;
}
The pointer could be all sorts of different things, and so performing an unconditional delete[]
on it is undefined. However, let's assume that we are indeed passing an array pointer,
int main()
{
int* arr = new int[5];
deleteForMe(arr);
return 0;
}
My question is, in this case where the pointer is an array, who is it that knows this? I mean, from the language/compiler's point of view, it has no idea whether or not arr
is an array pointer versus a pointer to a single int. Heck, it doesn't even know whether arr
was dynamically created. Yet, if I do the following instead,
int main()
{
int* num = new int(1);
deleteForMe(num);
return 0;
}
The OS is smart enough to only delete one int and not go on some type of 'killing spree' by deleting the rest of the memory beyond that point (contrast that with strlen
and a non-\0
-terminated string -- it will keep going until it hits 0).
So whose job is it to remember these things? Does the OS keep some type of record in the background? (I mean, I realise that I started this post by saying that what happens is undefined, but the fact is, the 'killing spree' scenario doesn't happen, so therefore in the practical world someone is remembering.)
One question that the answers given so far don't seem to address: if the runtime libraries (not the OS, really) can keep track of the number of things in the array, then why do we need the delete[]
syntax at all? Why can't a single delete
form be used to handle all deletes?
The answer to this goes back to C++'s roots as a C-compatible language (which it no longer really strives to be.) Stroustrup's philosophy was that the programmer should not have to pay for any features that they aren't using. If they're not using arrays, then they should not have to carry the cost of object arrays for every allocated chunk of memory.
That is, if your code simply does
Foo* foo = new Foo;
then the memory space that's allocated for foo
shouldn't include any extra overhead that would be needed to support arrays of Foo
.
Since only array allocations are set up to carry the extra array size information, you then need to tell the runtime libraries to look for that information when you delete the objects. That's why we need to use
delete[] bar;
instead of just
delete bar;
if bar is a pointer to an array.
For most of us (myself included), that fussiness about a few extra bytes of memory seems quaint these days. But there are still some situations where saving a few bytes (from what could be a very high number of memory blocks) can be important.
The compiler doesn't know it's an array, it's trusting the programmer. Deleting a pointer to a single int
with delete []
would result in undefined behavior. Your second main()
example is unsafe, even if it doesn't immediately crash.
The compiler does have to keep track of how many objects need to be deleted somehow. It may do this by over-allocating enough to store the array size. For more details, see the C++ Super FAQ.
Yes, the OS keeps some things in the 'background.' For example, if you run
int* num = new int[5];
the OS can allocate 4 extra bytes, store the size of the allocation in the first 4 bytes of the allocated memory and return an offset pointer (ie, it allocates memory spaces 1000 to 1024 but the pointer returned points to 1004, with locations 1000-1003 storing the size of the allocation). Then, when delete is called, it can look at 4 bytes before the pointer passed to it to find the size of the allocation.
I am sure that there are other ways of tracking the size of an allocation, but that's one option.
This is very similar to this question and it has many of the details your are looking for.
But suffice to say, it is not the job of the OS to track any of this. It's actually the runtime libraries or the underlying memory manager that will track the size of the array. This is usually done by allocating extra memory up front and storing the size of the array in that location (most use a head node).
This is viewable on some implementations by executing the following code
int* pArray = new int[5];
int size = *(pArray-1);