Growing Arrays with realloc
Often you don't know how many items you'll have until you've read them all. realloc resizes a heap block, and with it you can build a dynamic array that grows as needed — C's equivalent of a Python list or a Java ArrayList.
realloc
realloc(ptr, new_size) returns a pointer to a block of the new size, with the old contents preserved. The block may move to a new address, so always use the returned pointer:
#include <stdio.h> #include <stdlib.h> int main(void) { int *values = malloc(3 * sizeof *values); if (values == NULL) return 1; for (int i = 0; i < 3; i++) values[i] = (i + 1) * 10; int *bigger = realloc(values, 6 * sizeof *values); if (bigger == NULL) { // the old block is still valid and still ours free(values); return 1; } values = bigger; for (int i = 3; i < 6; i++) values[i] = (i + 1) * 10; for (int i = 0; i < 6; i++) printf("%d ", values[i]); printf("\n"); free(values); return 0; }
10 20 30 40 50 60
p = realloc(p, size); If realloc fails it returns NULL but leaves the original block allocated — assigning straight back to p loses your only pointer to it, leaking the memory. Use a temporary variable as above.A dynamic array
Keep three things together: the data pointer, how many items are in use, and how many fit (the capacity). When it's full, double the capacity — doubling keeps the average cost of adding an item constant:
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef struct { int *items; size_t count; size_t capacity; } IntList; bool list_push(IntList *list, int value) { if (list->count == list->capacity) { size_t new_cap = list->capacity ? list->capacity * 2 : 4; int *grown = realloc(list->items, new_cap * sizeof *grown); if (grown == NULL) return false; list->items = grown; list->capacity = new_cap; printf(" (grew to %zu)\n", new_cap); } list->items[list->count++] = value; return true; } void list_free(IntList *list) { free(list->items); *list = (IntList) { 0 }; } int main(void) { IntList list = { 0 }; for (int i = 1; i <= 10; i++) { if (!list_push(&list, i * i)) return 1; } for (size_t i = 0; i < list.count; i++) printf("%d ", list.items[i]); printf("\n%zu items, capacity %zu\n", list.count, list.capacity); list_free(&list); return 0; }
(grew to 4) (grew to 8) (grew to 16) 1 4 9 16 25 36 49 64 81 100 10 items, capacity 16
It uses a struct and typedef (lessons 40 and 44) and the -> operator for "member through a pointer" (lesson 41). realloc(NULL, size) behaves like malloc, so the first push needs no special case.
Reading an unknown number of lines
A real use: read every line of input into an array of strings, however many there are:
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char **lines = NULL; size_t count = 0, capacity = 0; char buffer[256]; while (fgets(buffer, sizeof buffer, stdin) != NULL) { buffer[strcspn(buffer, "\n")] = '\0'; if (count == capacity) { size_t new_cap = capacity ? capacity * 2 : 2; char **grown = realloc(lines, new_cap * sizeof *grown); if (grown == NULL) break; lines = grown; capacity = new_cap; } lines[count] = malloc(strlen(buffer) + 1); if (lines[count] == NULL) break; strcpy(lines[count], buffer); count++; } for (size_t i = count; i-- > 0; ) { // print in reverse order printf("%s\n", lines[i]); } for (size_t i = 0; i < count; i++) free(lines[i]); free(lines); return 0; }
first line second line third line
third line second line first line
Each line gets its own allocation, so clean-up frees every line first, then the array of pointers.
Shrinking
realloc can also shrink a block — for example to release spare capacity once all data is loaded. Shrinking may still return a different pointer, so treat it like growing.
Try it yourself
Read numbers until the input ends, storing them in a growing array, then print how many were read and the largest.
3.5 9.25 1 7 12.75 4
6 numbers, largest 12.75