How realloc Resizes Heap Allocation in C
In the C programming language, the realloc function
dynamically modifies the size of an existing heap-allocated memory
block. It achieves this by either resizing the memory chunk in place or
allocating a completely new block elsewhere on the heap, copying the
existing data over, and deallocating the original memory. Understanding
how realloc determines whether to shrink, expand in place,
or relocate is essential for writing efficient and safe C code.
In-Place Shrinking
When requested to reduce the size of an existing allocation,
realloc generally keeps the starting address of the buffer
unchanged. The memory allocator updates the block's internal metadata to
reflect the smaller size. Depending on the size reduction and the
allocator's internal threshold, the newly freed remainder at the tail of
the block is either split off and added back to the pool of free memory
for future allocations or kept as internal fragmentation.
In-Place Expansion
When increasing an allocation's size, realloc first
inspects the memory directly adjacent to the existing block:
- Contiguous Free Space: If the adjacent block of memory is unallocated and large enough to satisfy the additional size requested, the allocator merges the adjacent memory into the current block.
- Pointer Preservation: In this scenario, the
metadata is updated to represent the combined size, and
reallocreturns the original pointer since no data migration is necessary.
Relocation to a New Memory Block
If the memory directly following the current block is already
occupied or insufficient to accommodate the requested size,
realloc cannot expand in place:
- New Allocation: It searches the heap for a new, contiguous block of memory large enough to satisfy the requested size.
- Data Transfer: It copies the contents of the
original block to the new location, up to the lesser of the old and new
sizes (using an operation equivalent to
memcpy). - Deallocation: It frees the original memory block, returning it to the allocator's free list.
- Pointer Return: It returns the pointer to the newly allocated block. Any pointers referencing the original memory location become invalid dangling pointers.
Allocation Failures and Edge Cases
If the system lacks sufficient contiguous heap space to fulfill the
request, realloc returns NULL. When this
occurs, the original memory block remains untouched and is not freed.
Consequently, the return value must always be assigned to a temporary
pointer rather than directly overwriting the original pointer to prevent
memory leaks:
void *temp = realloc(original_ptr, new_size);
if (temp == NULL) {
// Handle allocation failure; original_ptr is still valid
} else {
original_ptr = temp;
}Additionally, realloc handles two distinct boundary
conditions:
ptr == NULL: If the pointer passed toreallocis null, the function behaves identically tomalloc(new_size).size == 0: Passing a size of zero with a non-null pointer historically acted likefree(ptr). However, modern C standards consider this implementation-defined or undefined, making it best practice to callfreedirectly instead.