Dynamic Memory Allocation · Lesson 26 of 35
realloc and Growable Arrays in C
What if you don't even know how many items are coming – reading numbers until the user stops, loading lines from a file? realloc resizes a heap block, keeping its contents. With it you can build a growable array, the C equivalent of Python's list or Java's ArrayList.
In this lesson
- How realloc works
- Using realloc safely
- A growable array that doubles its capacity
- Allocating a 2D array dynamically
realloc
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *a = malloc(3 * sizeof *a);
if (a == NULL) return 1;
for (int i = 0; i < 3; i++) a[i] = (i + 1) * 10;
int *bigger = realloc(a, 6 * sizeof *a); // ask for room for 6 ints
if (bigger == NULL) { // on failure the old block is still valid
free(a);
return 1;
}
a = bigger; // only now replace the old pointer
for (int i = 3; i < 6; i++) a[i] = (i + 1) * 10;
for (int i = 0; i < 6; i++) printf("%d ", a[i]);
printf("\n");
free(a);
return 0;
}
10 20 30 40 50 60
What realloc(p, new_size) does
Is there free space right after the block?
- Yesextends the block in place and returns the same address
- Noallocates a new block, copies the old contents, frees the old block, returns the new address
- Not enough memoryreturns NULL and leaves the old block untouched
Classic mistake: p = realloc(p, size);
If realloc fails it returns NULL – and you've just overwritten your only pointer to the old block, leaking it. Always assign to a temporary first, as above. Also remember the block may move: any other pointers into the old block become invalid.
A growable array
Growing by one element at a time would copy the whole array on every push. Doubling the capacity when it's full makes adding an element cheap on average:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct {
int *data;
size_t size; // elements in use
size_t capacity; // elements allocated
} IntList;
bool list_push(IntList *list, int value) {
if (list->size == list->capacity) {
size_t new_cap = list->capacity ? list->capacity * 2 : 4;
int *p = realloc(list->data, new_cap * sizeof *p);
if (p == NULL) return false;
list->data = p;
list->capacity = new_cap;
printf(" (grew to capacity %zu)\n", new_cap);
}
list->data[list->size++] = value;
return true;
}
void list_free(IntList *list) {
free(list->data);
list->data = NULL;
list->size = list->capacity = 0;
}
int main(void) {
IntList list = {NULL, 0, 0};
for (int i = 1; i <= 10; i++) {
if (!list_push(&list, i * i)) { list_free(&list); return 1; }
}
printf("%zu squares:", list.size);
for (size_t i = 0; i < list.size; i++) printf(" %d", list.data[i]);
printf("\n");
list_free(&list);
return 0;
}
(grew to capacity 4) (grew to capacity 8) (grew to capacity 16) 10 squares: 1 4 9 16 25 36 49 64 81 100
The struct bundles the pointer with its size and capacity (structures are covered in chapter 10; list->size means "the size member of the struct list points to"). Ten pushes caused only three reallocations.
A dynamic 2D array
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int rows = 3, cols = 4;
int *grid = malloc((size_t)rows * cols * sizeof *grid); // one block, row-major
if (grid == NULL) return 1;
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
grid[r * cols + c] = (r + 1) * (c + 1); // element (r, c)
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) printf("%4d", grid[r * cols + c]);
printf("\n");
}
free(grid);
return 0;
}
1 2 3 4 2 4 6 8 3 6 9 12
A single block with r * cols + c indexing is simple, fast and needs one free. The alternative – an array of row pointers, each malloc'd separately – allows rows of different lengths but needs a free per row plus one for the pointer array.
Practice
Try it yourself
Read numbers until 0 is entered, storing them in a growing array, then print how many were read and their maximum.
Show solution
#include <stdio.h> #include <stdlib.h> int main(void) { size_t n = 0, cap = 2; int *a = malloc(cap * sizeof *a); if (a == NULL) return 1; int x; printf("Numbers (0 to stop): "); while (scanf("%d", &x) == 1 && x != 0) { if (n == cap) { int *p = realloc(a, cap * 2 * sizeof *a); if (p == NULL) { free(a); return 1; } a = p; cap *= 2; } a[n++] = x; } int max = n ? a[0] : 0; for (size_t i = 1; i < n; i++) if (a[i] > max) max = a[i]; printf("read %zu numbers, max %d\n", n, max); free(a); return 0; }Numbers (0 to stop): 5 12 7 30 9 0 read 5 numbers, max 30
Quick check
Does realloc keep the old contents?
Yes, up to the smaller of the old and new sizes.
Why not write p = realloc(p, n)?
If it fails it returns NULL and you lose (leak) the original block.
Why double the capacity instead of adding 1?
Fewer reallocations and copies – pushes become cheap on average.
Summary
- realloc resizes a heap block, possibly moving it; assign the result to a temporary first.
- Growable arrays track size and capacity and double the capacity when full.
- A dynamic 2D array can be one block indexed with r * cols + c.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.