C Reallocate Memory
Introduction to Memory Reallocation
Memory reallocation is the process of resizing previously allocated memory blocks. The realloc() function allows you to adjust the size of dynamically allocated memory, making it larger or smaller as needed during program execution.
Key use cases for reallocation:
- Dynamic arrays that need to grow or shrink
- Buffers that need to accommodate variable-sized data
- Memory optimization by reducing oversized allocations
- Efficient memory usage in data structures
realloc() Function Syntax
Parameters:
- ptr: Pointer to previously allocated memory (can be NULL)
- new_size: New size in bytes for the memory block
Return value:
- Pointer to resized memory block, or NULL if reallocation fails
- On failure (NULL), the original memory block remains allocated and unchanged
void* realloc(void* ptr, size_t new_size);
Basic realloc() Usage
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(3 * sizeof *arr);
if (arr == NULL) {
printf("Initial allocation failed!\n");
return 1;
}
for (int i = 0; i < 3; i++) arr[i] = (i + 1) * 10;
printf("Original array: ");
for (int i = 0; i < 3; i++) printf("%d ", arr[i]);
int *tmp = realloc(arr, 6 * sizeof *arr); // use a temporary pointer
if (tmp == NULL) {
printf("\nReallocation failed! Keeping original size.\n");
free(arr);
return 1;
}
arr = tmp; // only assign on success
// Newly obtained elements have indeterminate values—initialize them
for (int i = 3; i < 6; i++) arr[i] = (i + 1) * 10;
printf("\nResized array: ");
for (int i = 0; i < 6; i++) printf("%d ", arr[i]);
printf("\n");
free(arr);
return 0;
}
Original array: 10 20 30 Resized array: 10 20 30 40 50 60
Safe realloc() Pattern
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(2 * sizeof *arr);
if (arr == NULL) return 1;
arr[0] = 100; arr[1] = 200;
int *temp = realloc(arr, 4 * sizeof *arr);
if (temp == NULL) {
printf("Reallocation failed! Original array preserved: %d %d\n", arr[0], arr[1]);
free(arr);
return 1;
}
arr = temp;
arr[2] = 300; arr[3] = 400;
printf("Resized successfully: ");
for (int i = 0; i < 4; i++) printf("%d ", arr[i]);
printf("\n");
free(arr);
return 0;
}
Resized successfully: 100 200 300 400
Overflow-Safe Growth (Capacity Doubling)
When computing sizes (count * sizeof(T)), guard against integer overflow before calling realloc(). Use size_t and check against SIZE_MAX.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int grow_int_array(int **p, size_t *cap) {
size_t new_cap = (*cap == 0) ? 1 : (*cap * 2);
if (new_cap > SIZE_MAX / sizeof **p) {
return 0; // overflow
}
int *tmp = realloc(*p, new_cap * sizeof **p);
if (!tmp) return 0;
*p = tmp;
*cap = new_cap;
return 1;
}
int main(void) {
int *a = NULL; size_t cap = 0;
if (!grow_int_array(&a, &cap)) { puts("grow failed"); return 1; }
printf("Capacity now %zu\n", cap);
free(a);
return 0;
}
realloc() with NULL Pointer
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = NULL; // no allocation yet
arr = realloc(arr, 3 * sizeof *arr); // behaves like malloc
if (arr == NULL) {
printf("Allocation failed!\n");
return 1;
}
for (int i = 0; i < 3; i++) arr[i] = i * 5;
printf("Array created with realloc(NULL): ");
for (int i = 0; i < 3; i++) printf("%d ", arr[i]);
printf("\n");
free(arr);
return 0;
}
Array created with realloc(NULL): 0 5 10
Shrinking Memory with realloc()
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(6 * sizeof *arr);
if (arr == NULL) return 1;
for (int i = 0; i < 6; i++) arr[i] = (i + 1) * 10;
printf("Original (6 elements): ");
for (int i = 0; i < 6; i++) printf("%d ", arr[i]);
int *temp = realloc(arr, 3 * sizeof *arr);
if (temp == NULL) {
printf("\nShrinking failed!\n");
free(arr);
return 1;
}
arr = temp;
printf("\nAfter shrinking (3 elements): ");
for (int i = 0; i < 3; i++) printf("%d ", arr[i]);
printf("\n");
free(arr);
return 0;
}
Original (6 elements): 10 20 30 40 50 60 After shrinking (3 elements): 10 20 30
realloc() Implementation Details
realloc() may work in different ways:
- In-place expansion: If there is free space after the current block, it may extend it without moving.
- Move + copy: If no space is available, it allocates a new block, copies the old data, and frees the old block.
- Bookkeeping change: On shrink, it may simply adjust internal metadata.
Regardless, you must assume the original pointer becomes invalid after a successful realloc() call (unless you kept it in a temp and compare), and any other pointers into the old block are indeterminate.
Common realloc() Pitfalls
- Assigning realloc() directly to the original pointer (risk of leak on failure).
- Using pointers into the old block after a successful reallocation (they may be dangling).
- Writing to newly grown region without initializing it first.
- Calling realloc() on memory not obtained from malloc/calloc/realloc (undefined behavior).
- Not guarding against size multiplication overflow (count * sizeof(T)).
- Relying on realloc(ptr, 0) return value—prefer free(ptr); ptr = NULL.
Best Practices
• Use a temporary pointer for realloc() and only assign on success.
• Initialize newly allocated regions after growth.
• Update related metadata (size/capacity) atomically with successful reallocation.
• Treat all other aliases/pointers into the block as invalid after reallocation.
• Check for integer overflow before computing byte sizes.
• Prefer free(ptr) + set ptr = NULL over realloc(ptr, 0) for clarity.