SiCrane mentions some very good points. I'm going to dive a little deeper into the internals of memory management and try to explain why the above is such a big problem.
There are various strategies for managing allocated memory. The allocator is what actually allocates the memory and frees it when you call new/delete. One very common allocation strategy is to request memory from the operating system when more is needed, but when its freed, don't immediately hand it back to the operating system. Keep it around. Why? Well, chances are the programmer is going to allocate more memory some time soon. If you give it all back to the OS right now, you'll just have to request more form the OS soon anyway (and these can all be pretty slow operations), so if the allocator hangs onto it, when you request more memory the allocator has some already available to give you. Yay, increase in speed! (there are other practical reasons why, but this is a big one) (and of course, if the allocator has a ton of free memory it's holding onto, it might decide to hand some of it back to the OS, but still keep a "reserve" of free memory hanging around).
Now, as you allocate memory and free it, your memory becomes fragmented. If you looked at it as one continuous block of memory, you'd have free chunks and allocated chunks mixed all over. How does the allocator keep track of what's allocated and what's free? Remember, this is the allocator! It can't just ask some other allocator for memory and keep a nice std::vector<ptr> of free and allocated chunks of memory. It's a pain in the butt to do, but here's a common way it's done:
Linked lists. In memory. When you allocate memory, the allocator does something like this:
void* malloc(size_t bytes) // same idea for new/new[] (horribly simplified, though)
{
void* ptr = RequestBytesFromOperatingSystem(bytes + HEADER_SIZE);
WRITE_HEADER(ptr, bytes + HEADER_SIZE); // writes header info to ptr, note this is being horribly simplified
return (char*)ptr + HEADER_SIZE; // skip the header and give return the usable block to the user
}
The allocator allocates an extra few bytes to keep a header for each block of memory. This header usually says how large the block is, if the block is free or allocated and in use, if the next block (at ptr + bytes + HEADER_SIZE) is free or allocated and in use, and if the previous block is free or allocated and in use. If the block is free, the allocator might use some of the space in the block to store pointers to the previous and next free blocks (maintaining a linked list).
So now let's take this idea for memory blocks, and what SiCrane said above, and document them to figure out how a block of memory might look to the allocator
/*
Potential structure of a memory block allocated by new (that is, this block is allocated and in use):
Header Tag (4 bytes)
Upper 29 bits: size of this block in memory
Bit 2: 1 if previous block is free, 0 if previous block is allocated
Bit 1: 1 if next block is free, 0 if next block is allocated
Bit 0: 1 if this block is free, 0 if this block is allocated
Pointer into segment table (4 bytes) (also acts as padding to maintain 8-byte alignment, like many CPUs need)
Instead of wasting the padding, we'll store which segment into our segment table this block should be
inserted into when its freed (so we don't have compute it when we free it, yay speed!)
User data (N bytes)
This is the memory the programmer is actually able to use
Potential structure of a memory block allocated by new [] (that is, this block is allocated and in use):
Header Tag (4 bytes)
Upper 29 bits: size of this block in memory
Bit 2: 1 if previous block is free, 0 if previous block is allocated
Bit 1: 1 if next block is free, 0 if next block is allocated
Bit 0: 1 if this block is free, 0 if this block is allocated
Number of elements (4 bytes) (also acts as padding to maintain 8-byte alignment, like many CPUs need)
Instead of wasting the padding, we'll use it to store the number of elements in this array
User data (N bytes)
This is the memory the programmer is actually able to use
Potential structure of a free block of memory (after it's been freed by delete or delete []):
Header Tag (4 bytes)
Upper 29 bits: size of this block in memory
Bit 2: 1 if previous block is free, 0 if previous block is allocated
Bit 1: 1 if next block is free, 0 if next block is allocated
Bit 0: 1 if this block is free, 0 if this block is allocated
Pointer to the next free block (4 bytes)
This way we can maintain a linked list of free blocks
Block data (N bytes)
Junk
*/