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 realloc returns 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:

  1. New Allocation: It searches the heap for a new, contiguous block of memory large enough to satisfy the requested size.
  2. 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).
  3. Deallocation: It frees the original memory block, returning it to the allocator's free list.
  4. 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 to realloc is null, the function behaves identically to malloc(new_size).
  • size == 0: Passing a size of zero with a non-null pointer historically acted like free(ptr). However, modern C standards consider this implementation-defined or undefined, making it best practice to call free directly instead.