Allocations Are Like a Game of Memory

Think of memory allocations and deallocations like a game of “memory”, where the only correct answer is to exactly match the cards. Failing to do so can lead to memory corruption that can sometimes be tricky to track down. The pairing should always be:

Alloc Dealloc
new delete
new[] delete[]
malloc/calloc/realloc free

If you mix and match (for instance, use new[] and then delete, or new and then free), you have a bug in your code. Each allocator in C++ serves a different purpose, and so is likely to be implemented in a different way. Because the various allocations are implemented differently, the deallocations will make assumptions about the format of the allocation — when you mix up the alloc and dealloc, those assumptions are no longer valid and the memory isn’t likely to be freed properly.

For example, when you do a vector allocation, the compiler will likely need to keep track of how many elements are in the array, so that it can call class destructors for the entire array properly when delete[] is called. One possible implementation is to store an extra four bytes of information for the number of array elements. So the vector new implementation may look something like this (pseudo-code):

void *vector_new_allocate( unsigned int numElements, unsigned int sizeOfElement )
{
	void *ret = ::malloc( sizeof( unsigned int ) + numElements * sizeOfElement );
	*((unsigned int *)ret) = numElements;
	return (void *)(((unsigned char *)ret) + sizeof( unsigned int ));
}

template< typename TYPE >
void *vector_new( TYPE t, unsigned int numElements )
{
	void *ret = vector_new_allocate( numElements, sizeof( t ) );
	for (unsigned int i = 0; i < numElements; ++i) {
		((TYPE *)ret)[ i ] = new TYPE();
	}
	return ret;
}

As you can see, the actual allocator may be reserving extra space to count the number of elements. This is helpful for implementing the vector delete function, which might look something like this:

void vector_delete_destroy( void *actual )
{
	::free( actual );
}

template< typename TYPE >
void vector_delete( TYPE *memory )
{
	void *actual = (void *)(((unsigned char *)memory) - sizeof( unsigned int ));
	unsigned int numElements = *(unsigned int *)actual;
	
	for (unsigned int i = 0; i < numElements; i++) {
		delete memory[ i ];
	}
	vector_delete_destroy( actual );
}

So what happens if you mismatch new with delete[]? In that case, the memory you’ve allocated won’t have those extra four byte preceding it, and so when the vector_delete method is called, it will get a random number of elements. It will likely delete the first item in the “array”, which is your object, and then proceed to call delete on potentially a whole lot of memory you don’t own. Needless to say, it’s not a very safe operation. However, my pseudo-code is just one possible example of how the vector new and delete operations may be implemented. Each compiler is free to implement them however they’d like, and that implementation can change from compiler version to version. The C++ specification leaves the behavior as “undefined” when passing in mismatched pointers. Working properly is one possibility. Crashing immediately is another. Corruption of memory is yet another. From the C++ language spec:

“In the first alternative (delete object), the value of the operand of delete shall be a pointer to a non-array object or a pointer to a sub-object (intro.object) representing a base class of such an object (clause class.derived). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array new-expression. If not, the behavior is undefined.”
http://www.kuzbass.ru:8086/docs/isocpp/expr.html#expr.delete (point -2-)

tl;dr: scalar new is different from vector new, and if you don’t match the delete properly, your code is wrong.

This entry was posted in C/C++ and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *