The realloc() function is one of the most powerful yet misunderstood tools in C's dynamic memory management arsenal. It allows programs to resize previously allocated memory blocks, enabling dynamic data structures to grow and shrink as needed. For C programmers, mastering realloc() is essential for building efficient, flexible applications that handle varying amounts of data without wasting memory.
What is realloc()?
realloc() (reallocation) is a standard library function that changes the size of a previously allocated memory block. It can expand or shrink the block, preserving the existing content while potentially moving the block to a new location if necessary. This flexibility makes it ideal for implementing dynamic arrays, buffers, and other data structures that need to adapt to changing requirements.
void *realloc(void *ptr, size_t new_size);
Why realloc() is Essential in C
- Dynamic Arrays: Grow arrays as elements are added
- Buffer Management: Resize buffers based on actual data size
- Memory Efficiency: Shrink allocations when less space is needed
- String Handling: Build strings of unknown length
- Data Structures: Implement resizable lists, stacks, queues
- Performance: Reduce reallocations by using growth strategies
Basic realloc() Usage
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// BASIC REALLOC() EXAMPLES
// ============================================================
void basicRealloc() {
printf("=== Basic realloc() Examples ===\n\n");
// Allocate initial array of 5 integers
int *arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL) {
printf("Initial allocation failed\n");
return;
}
// Initialize array
for (int i = 0; i < 5; i++) {
arr[i] = i * 10;
}
printf("Initial array (size 5): ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Expand array to 10 integers
int *new_arr = (int*)realloc(arr, 10 * sizeof(int));
if (new_arr == NULL) {
printf("Reallocation failed\n");
free(arr);
return;
}
arr = new_arr; // Update pointer
// Initialize new elements
for (int i = 5; i < 10; i++) {
arr[i] = i * 10;
}
printf("Expanded array (size 10): ");
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Shrink array back to 7 integers
new_arr = (int*)realloc(arr, 7 * sizeof(int));
if (new_arr == NULL) {
printf("Shrink failed\n");
free(arr);
return;
}
arr = new_arr;
printf("Shrunk array (size 7): ");
for (int i = 0; i < 7; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
}
int main() {
basicRealloc();
return 0;
}
realloc() Behavior and Return Values
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// UNDERSTANDING REALLOC() BEHAVIOR
// ============================================================
void demonstrateReallocBehavior() {
printf("=== realloc() Behavior ===\n\n");
// Initial allocation
int *arr = (int*)malloc(5 * sizeof(int));
printf("Initial allocation at: %p\n", (void*)arr);
for (int i = 0; i < 5; i++) {
arr[i] = i;
}
// Case 1: Expanding in place (if possible)
printf("\nAttempting to expand to 10 elements...\n");
int *new_arr = (int*)realloc(arr, 10 * sizeof(int));
if (new_arr == arr) {
printf(" ✅ Expanded in place at same address: %p\n", (void*)new_arr);
} else if (new_arr != NULL) {
printf(" 📦 Moved to new address: %p -> %p\n",
(void*)arr, (void*)new_arr);
arr = new_arr;
} else {
printf(" ❌ Reallocation failed\n");
free(arr);
return;
}
// Initialize new elements
for (int i = 5; i < 10; i++) {
arr[i] = i;
}
// Case 2: Shrinking (always in place)
printf("\nShrinking to 3 elements...\n");
new_arr = (int*)realloc(arr, 3 * sizeof(int));
if (new_arr == arr) {
printf(" ✅ Shrunk in place at: %p\n", (void*)new_arr);
} else {
printf(" ❌ Unexpected move during shrink\n");
if (new_arr) arr = new_arr;
}
printf("\nFinal array (first 3 elements preserved): ");
for (int i = 0; i < 3; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
}
void specialCases() {
printf("\n=== Special Cases ===\n\n");
// Case: realloc(NULL, size) acts like malloc
printf("realloc(NULL, 20) acts like malloc:\n");
int *p = (int*)realloc(NULL, 5 * sizeof(int));
if (p) {
printf(" Allocated at: %p\n", (void*)p);
free(p);
}
// Case: realloc(ptr, 0) acts like free (implementation-defined)
printf("\nrealloc(ptr, 0) behavior (implementation-defined):\n");
p = (int*)malloc(5 * sizeof(int));
printf(" Original pointer: %p\n", (void*)p);
void *result = realloc(p, 0);
printf(" After realloc to 0: %p\n", result);
// Note: Some implementations return NULL, some return a unique pointer
// Best practice: Don't rely on this - use free() explicitly
free(result); // Safe even if NULL
}
int main() {
demonstrateReallocBehavior();
specialCases();
return 0;
}
Dynamic Array Implementation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// DYNAMIC ARRAY WITH REALLOC()
// ============================================================
typedef struct {
int *data;
int size; // Current number of elements
int capacity; // Allocated capacity
} DynamicArray;
// Initialize dynamic array
DynamicArray* createArray(int initialCapacity) {
DynamicArray *arr = (DynamicArray*)malloc(sizeof(DynamicArray));
if (arr == NULL) return NULL;
arr->data = (int*)malloc(initialCapacity * sizeof(int));
if (arr->data == NULL) {
free(arr);
return NULL;
}
arr->size = 0;
arr->capacity = initialCapacity;
return arr;
}
// Resize array to new capacity
int resizeArray(DynamicArray *arr, int newCapacity) {
if (newCapacity < arr->size) {
// Can't resize smaller than current size
return 0;
}
int *newData = (int*)realloc(arr->data, newCapacity * sizeof(int));
if (newData == NULL) {
return 0; // Reallocation failed
}
arr->data = newData;
arr->capacity = newCapacity;
printf("Resized: capacity %d -> %d\n", arr->capacity, newCapacity);
return 1;
}
// Add element to end
int append(DynamicArray *arr, int value) {
// Check if we need to grow
if (arr->size >= arr->capacity) {
int newCapacity = arr->capacity * 2; // Double the capacity
if (!resizeArray(arr, newCapacity)) {
return 0; // Failed to grow
}
}
arr->data[arr->size++] = value;
return 1;
}
// Insert element at index
int insertAt(DynamicArray *arr, int index, int value) {
if (index < 0 || index > arr->size) {
return 0; // Invalid index
}
// Ensure space
if (arr->size >= arr->capacity) {
int newCapacity = arr->capacity * 2;
if (!resizeArray(arr, newCapacity)) {
return 0;
}
}
// Shift elements right
for (int i = arr->size; i > index; i--) {
arr->data[i] = arr->data[i - 1];
}
arr->data[index] = value;
arr->size++;
return 1;
}
// Remove element at index
int removeAt(DynamicArray *arr, int index) {
if (index < 0 || index >= arr->size) {
return 0;
}
// Shift elements left
for (int i = index; i < arr->size - 1; i++) {
arr->data[i] = arr->data[i + 1];
}
arr->size--;
// Optional: shrink if size < capacity/4
if (arr->size > 0 && arr->size < arr->capacity / 4) {
int newCapacity = arr->capacity / 2;
if (newCapacity < 4) newCapacity = 4; // Minimum capacity
resizeArray(arr, newCapacity);
}
return 1;
}
// Get element at index
int get(DynamicArray *arr, int index, int *value) {
if (index < 0 || index >= arr->size) {
return 0;
}
*value = arr->data[index];
return 1;
}
// Print array
void printArray(DynamicArray *arr) {
printf("Array (size=%d, cap=%d): [", arr->size, arr->capacity);
for (int i = 0; i < arr->size; i++) {
printf("%d", arr->data[i]);
if (i < arr->size - 1) printf(", ");
}
printf("]\n");
}
// Free array
void freeArray(DynamicArray *arr) {
if (arr) {
free(arr->data);
free(arr);
}
}
int main() {
printf("=== Dynamic Array Implementation ===\n\n");
DynamicArray *arr = createArray(4);
printf("Appending elements:\n");
for (int i = 1; i <= 10; i++) {
append(arr, i * 10);
printf(" Added %d: ", i * 10);
printArray(arr);
}
printf("\nInserting 99 at index 3:\n");
insertAt(arr, 3, 99);
printArray(arr);
printf("\nRemoving element at index 5:\n");
removeAt(arr, 5);
printArray(arr);
printf("\nRemoving multiple elements to trigger shrink:\n");
for (int i = 0; i < 6; i++) {
removeAt(arr, arr->size - 1);
printArray(arr);
}
freeArray(arr);
return 0;
}
String Building with realloc()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// DYNAMIC STRING BUILDING
// ============================================================
typedef struct {
char *data;
int length;
int capacity;
} StringBuilder;
StringBuilder* createStringBuilder(int initialCapacity) {
StringBuilder *sb = (StringBuilder*)malloc(sizeof(StringBuilder));
if (sb == NULL) return NULL;
sb->data = (char*)malloc(initialCapacity);
if (sb->data == NULL) {
free(sb);
return NULL;
}
sb->data[0] = '\0';
sb->length = 0;
sb->capacity = initialCapacity;
return sb;
}
int ensureCapacity(StringBuilder *sb, int needed) {
if (sb->length + needed >= sb->capacity) {
int newCapacity = sb->capacity * 2;
while (sb->length + needed >= newCapacity) {
newCapacity *= 2;
}
char *newData = (char*)realloc(sb->data, newCapacity);
if (newData == NULL) {
return 0;
}
sb->data = newData;
sb->capacity = newCapacity;
printf(" String builder resized to %d\n", newCapacity);
}
return 1;
}
int appendString(StringBuilder *sb, const char *str) {
int len = strlen(str);
if (!ensureCapacity(sb, len + 1)) {
return 0;
}
strcpy(sb->data + sb->length, str);
sb->length += len;
return 1;
}
int appendChar(StringBuilder *sb, char c) {
if (!ensureCapacity(sb, 2)) { // char + null terminator
return 0;
}
sb->data[sb->length++] = c;
sb->data[sb->length] = '\0';
return 1;
}
int appendInt(StringBuilder *sb, int value) {
char buffer[20];
sprintf(buffer, "%d", value);
return appendString(sb, buffer);
}
int appendFloat(StringBuilder *sb, double value, int precision) {
char buffer[50];
char format[10];
sprintf(format, "%%.%df", precision);
sprintf(buffer, format, value);
return appendString(sb, buffer);
}
void clearStringBuilder(StringBuilder *sb) {
sb->length = 0;
sb->data[0] = '\0';
}
void freeStringBuilder(StringBuilder *sb) {
if (sb) {
free(sb->data);
free(sb);
}
}
int main() {
printf("=== Dynamic String Building ===\n\n");
StringBuilder *sb = createStringBuilder(10);
printf("Building a sentence:\n");
appendString(sb, "The ");
appendString(sb, "quick ");
appendString(sb, "brown ");
appendString(sb, "fox ");
appendString(sb, "jumps ");
appendString(sb, "over ");
appendString(sb, "the ");
appendString(sb, "lazy ");
appendString(sb, "dog.");
printf(" Result: %s\n", sb->data);
printf(" Length: %d, Capacity: %d\n\n", sb->length, sb->capacity);
printf("Adding numbers:\n");
clearStringBuilder(sb);
appendString(sb, "Values: ");
for (int i = 1; i <= 5; i++) {
appendInt(sb, i * 10);
if (i < 5) appendString(sb, ", ");
}
printf(" Result: %s\n", sb->data);
printf("\nBuilding CSV line:\n");
clearStringBuilder(sb);
appendString(sb, "Alice,30,55000.50,");
appendFloat(sb, 3.14159, 2);
appendString(sb, ",");
appendFloat(sb, 2.71828, 3);
printf(" Result: %s\n", sb->data);
freeStringBuilder(sb);
return 0;
}
Growth Strategies and Performance
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// ============================================================
// GROWTH STRATEGIES COMPARISON
// ============================================================
// Strategy 1: Add fixed amount each time
void fixedGrowthStrategy(int iterations, int fixedIncrement) {
int *arr = NULL;
int capacity = 0;
int size = 0;
for (int i = 0; i < iterations; i++) {
if (size >= capacity) {
capacity += fixedIncrement;
int *new_arr = (int*)realloc(arr, capacity * sizeof(int));
if (new_arr == NULL) {
free(arr);
return;
}
arr = new_arr;
}
arr[size++] = i;
}
free(arr);
}
// Strategy 2: Double each time
void doubleGrowthStrategy(int iterations) {
int *arr = NULL;
int capacity = 0;
int size = 0;
for (int i = 0; i < iterations; i++) {
if (size >= capacity) {
capacity = capacity == 0 ? 1 : capacity * 2;
int *new_arr = (int*)realloc(arr, capacity * sizeof(int));
if (new_arr == NULL) {
free(arr);
return;
}
arr = new_arr;
}
arr[size++] = i;
}
free(arr);
}
// Strategy 3: Fibonacci growth
void fibonacciGrowthStrategy(int iterations) {
int *arr = NULL;
int capacity = 0;
int size = 0;
int a = 0, b = 1;
for (int i = 0; i < iterations; i++) {
if (size >= capacity) {
int next = a + b;
capacity = next == 0 ? 1 : next;
a = b;
b = next;
int *new_arr = (int*)realloc(arr, capacity * sizeof(int));
if (new_arr == NULL) {
free(arr);
return;
}
arr = new_arr;
}
arr[size++] = i;
}
free(arr);
}
void compareGrowthStrategies() {
const int ITERATIONS = 100000;
clock_t start, end;
printf("=== Growth Strategy Comparison ===\n");
printf("Iterations: %d\n\n", ITERATIONS);
// Fixed growth (add 1000 each time)
start = clock();
fixedGrowthStrategy(ITERATIONS, 1000);
end = clock();
printf("Fixed growth (+1000): %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
// Double growth
start = clock();
doubleGrowthStrategy(ITERATIONS);
end = clock();
printf("Double growth (x2): %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
// Fibonacci growth
start = clock();
fibonacciGrowthStrategy(ITERATIONS);
end = clock();
printf("Fibonacci growth: %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
printf("\nAnalysis:\n");
printf(" - Fixed growth: O(n²) reallocations, good for predictable growth\n");
printf(" - Double growth: O(log n) reallocations, good average case\n");
printf(" - Fibonacci: similar to double but more moderate growth\n");
}
int main() {
compareGrowthStrategies();
return 0;
}
Reallocating 2D Arrays
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// REALLOCATING 2D ARRAYS
// ============================================================
typedef struct {
int **data;
int rows;
int cols;
int rowCapacity;
} DynamicMatrix;
DynamicMatrix* createMatrix(int initialRows, int initialCols) {
DynamicMatrix *mat = (DynamicMatrix*)malloc(sizeof(DynamicMatrix));
if (mat == NULL) return NULL;
mat->rowCapacity = initialRows;
mat->rows = 0;
mat->cols = initialCols;
mat->data = (int**)malloc(initialRows * sizeof(int*));
if (mat->data == NULL) {
free(mat);
return NULL;
}
// Allocate each row (lazy allocation)
for (int i = 0; i < initialRows; i++) {
mat->data[i] = NULL;
}
return mat;
}
int addRow(DynamicMatrix *mat) {
// Check if we need more row pointers
if (mat->rows >= mat->rowCapacity) {
int newCapacity = mat->rowCapacity * 2;
int **newData = (int**)realloc(mat->data, newCapacity * sizeof(int*));
if (newData == NULL) {
return 0;
}
// Initialize new pointers to NULL
for (int i = mat->rowCapacity; i < newCapacity; i++) {
newData[i] = NULL;
}
mat->data = newData;
mat->rowCapacity = newCapacity;
printf(" Row capacity increased to %d\n", newCapacity);
}
// Allocate the new row
mat->data[mat->rows] = (int*)malloc(mat->cols * sizeof(int));
if (mat->data[mat->rows] == NULL) {
return 0;
}
// Initialize row
for (int j = 0; j < mat->cols; j++) {
mat->data[mat->rows][j] = 0;
}
mat->rows++;
return 1;
}
int addColumn(DynamicMatrix *mat) {
// Need to reallocate each row
for (int i = 0; i < mat->rows; i++) {
int *newRow = (int*)realloc(mat->data[i], (mat->cols + 1) * sizeof(int));
if (newRow == NULL) {
return 0;
}
mat->data[i] = newRow;
mat->data[i][mat->cols] = 0; // Initialize new column
}
mat->cols++;
printf(" Added column, now %d columns\n", mat->cols);
return 1;
}
void setElement(DynamicMatrix *mat, int row, int col, int value) {
if (row >= 0 && row < mat->rows && col >= 0 && col < mat->cols) {
mat->data[row][col] = value;
}
}
void printMatrix(DynamicMatrix *mat) {
printf("Matrix (%d x %d):\n", mat->rows, mat->cols);
for (int i = 0; i < mat->rows; i++) {
printf(" Row %d: ", i);
for (int j = 0; j < mat->cols; j++) {
printf("%4d ", mat->data[i][j]);
}
printf("\n");
}
}
void freeMatrix(DynamicMatrix *mat) {
if (mat) {
for (int i = 0; i < mat->rows; i++) {
free(mat->data[i]);
}
free(mat->data);
free(mat);
}
}
int main() {
printf("=== Dynamic 2D Matrix ===\n\n");
DynamicMatrix *mat = createMatrix(3, 3);
printf("Adding rows:\n");
for (int i = 0; i < 5; i++) {
addRow(mat);
for (int j = 0; j < mat->cols; j++) {
setElement(mat, i, j, (i + 1) * (j + 1));
}
}
printf("\n");
printMatrix(mat);
printf("\nAdding a column:\n");
addColumn(mat);
// Fill new column
for (int i = 0; i < mat->rows; i++) {
setElement(mat, i, mat->cols - 1, (i + 1) * 10);
}
printf("\n");
printMatrix(mat);
freeMatrix(mat);
return 0;
}
Error Handling and Safety
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// ============================================================
// SAFE REALLOC() WRAPPERS
// ============================================================
// Safe realloc with error handling
void* safeRealloc(void *ptr, size_t new_size, const char *func, int line) {
void *new_ptr = realloc(ptr, new_size);
if (new_ptr == NULL && new_size > 0) {
fprintf(stderr, "realloc failed at %s:%d - ", func, line);
fprintf(stderr, "requested %zu bytes\n", new_size);
perror("Error details");
}
return new_ptr;
}
#define SAFE_REALLOC(ptr, size) safeRealloc(ptr, size, __func__, __LINE__)
// Safe realloc that preserves data on failure
int safeReallocPreserve(void **ptr, size_t new_size) {
void *new_ptr = realloc(*ptr, new_size);
if (new_ptr == NULL && new_size > 0) {
return 0; // Failed, original pointer still valid
}
*ptr = new_ptr;
return 1; // Success
}
// Realloc with logging
typedef struct {
void *ptr;
size_t size;
const char *file;
int line;
} AllocationInfo;
AllocationInfo allocations[1000];
int allocCount = 0;
void* trackedRealloc(void *ptr, size_t new_size, const char *file, int line) {
void *new_ptr = realloc(ptr, new_size);
if (new_ptr != NULL) {
// Log the reallocation
if (allocCount < 1000) {
allocations[allocCount].ptr = new_ptr;
allocations[allocCount].size = new_size;
allocations[allocCount].file = file;
allocations[allocCount].line = line;
allocCount++;
}
printf("Realloc: %p -> %p, size: %zu at %s:%d\n",
ptr, new_ptr, new_size, file, line);
}
return new_ptr;
}
#define TRACKED_REALLOC(ptr, size) trackedRealloc(ptr, size, __FILE__, __LINE__)
// Safe growth macro with overflow checking
#define SAFE_GROW(current, factor) \
(((current) > (SIZE_MAX / (factor))) ? SIZE_MAX : (current) * (factor))
void demonstrateSafety() {
printf("=== Safe Realloc Practices ===\n\n");
int *arr = NULL;
int capacity = 0;
int size = 0;
// Safe growth with overflow check
for (int i = 0; i < 10; i++) {
if (size >= capacity) {
size_t new_capacity = SAFE_GROW(capacity + 1, 2);
if (new_capacity == SIZE_MAX) {
printf("Growth would overflow!\n");
break;
}
int *new_arr = SAFE_REALLOC(arr, new_capacity * sizeof(int));
if (new_arr == NULL) {
printf("Reallocation failed\n");
break;
}
arr = new_arr;
capacity = new_capacity;
printf("Grew to capacity %d\n", capacity);
}
arr[size++] = i;
}
// Try to allocate huge amount (should fail gracefully)
printf("\nAttempting huge allocation:\n");
int *huge = SAFE_REALLOC(NULL, SIZE_MAX);
if (huge == NULL) {
printf(" Huge allocation correctly failed\n");
}
free(arr);
}
int main() {
demonstrateSafety();
return 0;
}
Memory Pool with realloc()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// MEMORY POOL WITH REALLOC()
// ============================================================
typedef struct {
void **blocks;
int count;
int capacity;
size_t totalAllocated;
} MemoryPool;
MemoryPool* createPool(int initialCapacity) {
MemoryPool *pool = (MemoryPool*)malloc(sizeof(MemoryPool));
if (pool == NULL) return NULL;
pool->blocks = (void**)malloc(initialCapacity * sizeof(void*));
if (pool->blocks == NULL) {
free(pool);
return NULL;
}
pool->count = 0;
pool->capacity = initialCapacity;
pool->totalAllocated = 0;
return pool;
}
void* poolAlloc(MemoryPool *pool, size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) return NULL;
// Grow blocks array if needed
if (pool->count >= pool->capacity) {
int newCapacity = pool->capacity * 2;
void **newBlocks = (void**)realloc(pool->blocks,
newCapacity * sizeof(void*));
if (newBlocks == NULL) {
free(ptr);
return NULL;
}
pool->blocks = newBlocks;
pool->capacity = newCapacity;
}
pool->blocks[pool->count++] = ptr;
pool->totalAllocated += size;
return ptr;
}
void* poolRealloc(MemoryPool *pool, void *ptr, size_t new_size, size_t old_size) {
// Find the block in our pool
int index = -1;
for (int i = 0; i < pool->count; i++) {
if (pool->blocks[i] == ptr) {
index = i;
break;
}
}
if (index == -1 && ptr != NULL) {
// Pointer not in pool - can't realloc
return NULL;
}
void *new_ptr = realloc(ptr, new_size);
if (new_ptr == NULL && new_size > 0) {
return NULL;
}
if (new_ptr != ptr) {
// Pointer moved - update pool
if (ptr == NULL) {
// New allocation
if (pool->count >= pool->capacity) {
int newCapacity = pool->capacity * 2;
void **newBlocks = (void**)realloc(pool->blocks,
newCapacity * sizeof(void*));
if (newBlocks == NULL) {
free(new_ptr);
return NULL;
}
pool->blocks = newBlocks;
pool->capacity = newCapacity;
}
pool->blocks[pool->count++] = new_ptr;
} else {
// Update existing entry
pool->blocks[index] = new_ptr;
}
}
if (ptr == NULL) {
pool->totalAllocated += new_size;
} else {
pool->totalAllocated += (new_size - old_size);
}
return new_ptr;
}
void poolFree(MemoryPool *pool, void *ptr) {
if (ptr == NULL) return;
// Find and remove from pool
for (int i = 0; i < pool->count; i++) {
if (pool->blocks[i] == ptr) {
free(ptr);
// Shift remaining blocks
for (int j = i; j < pool->count - 1; j++) {
pool->blocks[j] = pool->blocks[j + 1];
}
pool->count--;
return;
}
}
}
void poolFreeAll(MemoryPool *pool) {
for (int i = 0; i < pool->count; i++) {
free(pool->blocks[i]);
}
pool->count = 0;
pool->totalAllocated = 0;
}
void destroyPool(MemoryPool *pool) {
poolFreeAll(pool);
free(pool->blocks);
free(pool);
}
void printPoolStats(MemoryPool *pool) {
printf("Pool stats:\n");
printf(" Blocks: %d/%d\n", pool->count, pool->capacity);
printf(" Total allocated: %zu bytes\n", pool->totalAllocated);
}
int main() {
printf("=== Memory Pool with realloc() ===\n\n");
MemoryPool *pool = createPool(4);
// Allocate some memory
int *p1 = (int*)poolAlloc(pool, 10 * sizeof(int));
int *p2 = (int*)poolAlloc(pool, 20 * sizeof(int));
char *p3 = (char*)poolAlloc(pool, 100);
printPoolStats(pool);
// Reallocate (grow)
printf("\nGrowing p2 from 20 to 40 ints:\n");
p2 = (int*)poolRealloc(pool, p2, 40 * sizeof(int), 20 * sizeof(int));
printPoolStats(pool);
// Free one block
printf("\nFreeing p1:\n");
poolFree(pool, p1);
printPoolStats(pool);
// Free all
printf("\nFreeing all:\n");
poolFreeAll(pool);
printPoolStats(pool);
destroyPool(pool);
return 0;
}
Common Pitfalls and Best Practices
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// COMMON PITFALLS
// ============================================================
// PITFALL 1: Not checking return value
void pitfall1() {
int *arr = (int*)malloc(10 * sizeof(int));
// ... use arr ...
// WRONG - doesn't check if realloc succeeded
arr = realloc(arr, 20 * sizeof(int)); // Lost original pointer if fails!
// CORRECT
int *new_arr = realloc(arr, 20 * sizeof(int));
if (new_arr != NULL) {
arr = new_arr;
} else {
// Handle error - arr still points to original memory
}
}
// PITFALL 2: Using realloc on non-malloc'd memory
void pitfall2() {
int stack_array[10];
// int *new_array = realloc(stack_array, 20 * sizeof(int)); // UNDEFINED!
}
// PITFALL 3: Forgetting to update all pointers
void pitfall3() {
int *arr = malloc(10 * sizeof(int));
int *ptr = arr + 5; // Points into the array
arr = realloc(arr, 20 * sizeof(int)); // arr may move
// ptr may now be invalid if arr moved!
*ptr = 42; // UNDEFINED if realloc moved the memory
}
// PITFALL 4: Realloc with size 0 (implementation-defined)
void pitfall4() {
int *arr = malloc(10 * sizeof(int));
// Some implementations return NULL, some return a unique pointer
void *result = realloc(arr, 0);
// Don't rely on behavior - use free() explicitly
free(result); // Safe even if NULL
}
// PITFALL 5: Not preserving data on failure
void pitfall5() {
int *arr = malloc(10 * sizeof(int));
// Fill arr...
// WRONG - if realloc fails, original data is lost
arr = realloc(arr, 1000 * sizeof(int)); // Original pointer lost if fails!
// CORRECT
int *new_arr = realloc(arr, 1000 * sizeof(int));
if (new_arr != NULL) {
arr = new_arr;
} else {
// arr still valid, can continue
}
}
// ============================================================
// BEST PRACTICES
// ============================================================
// Best Practice 1: Use temporary pointer
int* safeRealloc(int *ptr, size_t new_size) {
int *new_ptr = (int*)realloc(ptr, new_size);
if (new_ptr == NULL && new_size > 0) {
// Handle error - original ptr still valid
return NULL;
}
return new_ptr;
}
// Best Practice 2: Track size separately
typedef struct {
int *data;
size_t size;
size_t capacity;
} SafeArray;
SafeArray* createSafeArray(size_t initialCapacity) {
SafeArray *arr = malloc(sizeof(SafeArray));
arr->data = malloc(initialCapacity * sizeof(int));
arr->size = 0;
arr->capacity = initialCapacity;
return arr;
}
int safeArrayAppend(SafeArray *arr, int value) {
if (arr->size >= arr->capacity) {
size_t newCapacity = arr->capacity * 2;
int *new_data = realloc(arr->data, newCapacity * sizeof(int));
if (new_data == NULL) {
return 0;
}
arr->data = new_data;
arr->capacity = newCapacity;
}
arr->data[arr->size++] = value;
return 1;
}
// Best Practice 3: Growth factor strategy
#define GROWTH_FACTOR 1.5
size_t calculateNewCapacity(size_t current, size_t min_needed) {
size_t new_capacity = (size_t)(current * GROWTH_FACTOR);
while (new_capacity < min_needed) {
new_capacity = (size_t)(new_capacity * GROWTH_FACTOR);
}
return new_capacity;
}
// Best Practice 4: Shrink when appropriate
void maybeShrink(SafeArray *arr) {
if (arr->size > 0 && arr->size < arr->capacity / 4) {
size_t new_capacity = arr->capacity / 2;
if (new_capacity < 8) new_capacity = 8;
int *new_data = realloc(arr->data, new_capacity * sizeof(int));
if (new_data != NULL) {
arr->data = new_data;
arr->capacity = new_capacity;
}
}
}
// Best Practice 5: Use realloc for NULL (acts like malloc)
void* flexibleAlloc(size_t size) {
return realloc(NULL, size); // Same as malloc
}
// Best Practice 6: Use macros for common patterns
#define REALLOC_OR_DIE(ptr, size) \
do { \
void *new_ptr = realloc(ptr, size); \
if (new_ptr == NULL && size > 0) { \
fprintf(stderr, "Fatal: realloc failed at %s:%d\n", \
__FILE__, __LINE__); \
exit(1); \
} \
ptr = new_ptr; \
} while (0)
int main() {
printf("=== Best Practices Example ===\n\n");
SafeArray *arr = createSafeArray(4);
printf("Appending elements (automatic growth):\n");
for (int i = 1; i <= 10; i++) {
safeArrayAppend(arr, i * 10);
printf(" Added %d: size=%zu, capacity=%zu\n",
i * 10, arr->size, arr->capacity);
}
printf("\nShrinking after removals:\n");
for (int i = 0; i < 8; i++) {
arr->size--;
maybeShrink(arr);
printf(" After removal: size=%zu, capacity=%zu\n",
arr->size, arr->capacity);
}
free(arr->data);
free(arr);
return 0;
}
Summary Table
| Operation | Effect | Return Value | Notes |
|---|---|---|---|
realloc(NULL, size) | Same as malloc(size) | New pointer or NULL | Convenient for first allocation |
realloc(ptr, 0) | Implementation-defined | NULL or unique pointer | Avoid - use free() instead |
realloc(ptr, larger) | Expand block | Same pointer or new one | Content preserved up to original size |
realloc(ptr, smaller) | Shrink block | Usually same pointer | Content preserved up to new size |
realloc(ptr, size) fails | Returns NULL | NULL | Original block still valid |
Conclusion
realloc() is a powerful tool for dynamic memory management in C. Key takeaways:
- Always check return values - realloc can fail and return NULL
- Use temporary pointers - preserve original on failure
- Track size separately - know how much memory you have
- Choose growth strategy wisely - double growth is common, but consider trade-offs
- Shrink when appropriate - free unused memory
- Handle edge cases - NULL pointer, zero size, overflow
- Consider memory pools - for many small allocations
Best practices:
- Use growth factor (1.5x or 2x) for amortized constant time
- Check for integer overflow when calculating new size
- Consider shrinking when usage drops significantly
- Document ownership and lifetime
- Use tools like Valgrind to detect leaks
Mastering realloc() enables you to build efficient, flexible data structures that adapt to changing requirements while minimizing memory waste.