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:

C
#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;
}
Output
10 20 30 40 50 60
Don't write 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:

C
#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;
}
Output
  (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:

C
#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;
}
Input
first line
second line
third line
Output
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.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Input
3.5 9.25 1 7
12.75 4
Output
6 numbers, largest 12.75