Skip to content
FollowtheDamnCode

free and realloc

Giving memory back, and resizing it.

5 minute read

Big idea

Every block from malloc or calloc must be given back with free. realloc resizes a block — growing it when you need more room.

free

free(nums);
nums = NULL;
  • free(p) returns the block to the system. After this, p still holds the old address, but that memory isn't yours any more.
  • Setting p = NULL afterwards is a good habit: using it by mistake then fails loudly instead of silently corrupting memory.
  • Free exactly once. Freeing the same block twice (a double free) can crash the program.

Forgetting to free is a memory leak: the block stays reserved until the program exits. In a small program it's harmless; in a server that runs for weeks, leaks pile up until memory runs out.

realloc: grow as you go

int *bigger = realloc(nums, new_count * sizeof(int));

realloc keeps the old contents and gives you a block of the new size. It may move the data to a new address, so always use the pointer it returns. And if it fails it returns NULL — without freeing the old block — so assign to a temporary first:

A list that doubles its capacity as it fills
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int capacity = 2, count = 0;
    int *list = malloc(capacity * sizeof(int));
    if (list == NULL) return 1;

    int value;
    while (scanf("%d", &value) == 1) {
        if (count == capacity) {
            capacity *= 2;
            int *grown = realloc(list, capacity * sizeof(int));
            if (grown == NULL) {
                free(list);
                return 1;
            }
            list = grown;
            printf("(grew to %d)\n", capacity);
        }
        list[count++] = value;
    }

    for (int i = 0; i < count; i++) printf("%d ", list[i]);
    printf("\n");
    free(list);
    return 0;
}
input
5 8 13 21 34 55
output
(grew to 4)
(grew to 8)
5 8 13 21 34 55 

Run and edit it in the app.

Doubling means only a few resizes even for thousands of values — this is how dynamic arrays in most languages work under the hood.

Watch it

Why the temporary?

list = realloc(list, 4 * sizeof(int));        // ✗ if this returns NULL, the only
                                              //   copy of the old address is gone

int *grown = realloc(list, 4 * sizeof(int));  // ✓ the old block is still in list
if (grown == NULL) {
    free(list);                               //   so it can be freed properly
    return 1;
}
list = grown;

The first version works whenever realloc succeeds — which is why the bug survives testing. But on the day memory runs out, it leaks the original block and loses the data in it.

Why store realloc's result in a temporary pointer first?

The rules, in one place

  1. Every malloc/calloc/realloc → exactly one free.
  2. Check every allocation for NULL.
  3. Never use memory after freeing it; set the pointer to NULL.
  4. realloc into a temporary.

Think of it like

Borrowing chairs for a party. free returns them. realloc swaps your 4 chairs for 8 — maybe from a different stack, so check where they are now. Forget to return them and the rental shop runs out.

🕷️ Spidey-sense: Everything I web up, I clean up. Leave webs all over Queens and eventually there's no room to swing — that's a memory leak.

Key terms

  • free — Returns heap memory.
  • realloc — Resizes a heap block, possibly moving it.
  • Memory leak — Memory that's never freed.
  • Dangling pointer — A pointer to memory that has been freed.
  • Double free — Freeing the same block twice.

Check yourself

What is a memory leak?

After free(p), p…

realloc may…