Dynamic Memory with malloc and free

Local variables disappear when their function returns, and array sizes must normally be fixed in advance. When you need memory whose size is only known at run time, or that must outlive a function call, you request it from the heap with malloc — and give it back with free when you're done.

Stack and heap

Stack (automatic)Heap (dynamic)
Used forLocal variables, function callsMemory you request with malloc
SizeFixed when compiled; small (often 1–8 MB total)Chosen at run time; can be very large
LifetimeEnds with the blockUntil you call free
Managed byThe compiler, automaticallyYou

malloc and free

malloc(n) (from <stdlib.h>) reserves n bytes and returns a pointer to them, or NULL if it can't:

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    size_t count = 5;
    int *squares = malloc(count * sizeof *squares);
    if (squares == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    for (size_t i = 0; i < count; i++) {
        squares[i] = (int) (i * i);
    }
    for (size_t i = 0; i < count; i++) {
        printf("%d ", squares[i]);
    }
    printf("\n");
    free(squares);
    squares = NULL;
    return 0;
}
Output
0 1 4 9 16

Points to notice:

  • count * sizeof *squares — the size in bytes. Using sizeof *pointer instead of sizeof(int) stays correct if the type ever changes.
  • Check for NULL. Allocation can fail; using a null pointer would crash.
  • No cast needed. malloc returns void *, which converts to any object pointer type automatically in C. (C++ requires a cast; C doesn't.)
  • The memory is uninitialised — it contains garbage until you write to it.
  • free exactly once, then set the pointer to NULL so it can't be used by accident.

calloc: zeroed memory

calloc(count, size) allocates an array and sets every byte to zero. It also checks that count * size doesn't overflow:

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *votes = calloc(4, sizeof *votes);
    if (votes == NULL) return 1;
    int ballots[] = { 2, 0, 2, 3, 2, 1, 0, 2 };
    for (int i = 0; i < 8; i++) {
        votes[ballots[i]]++;
    }
    for (int c = 0; c < 4; c++) {
        printf("candidate %d: %d votes\n", c, votes[c]);
    }
    free(votes);
    return 0;
}
Output
candidate 0: 2 votes
candidate 1: 1 votes
candidate 2: 4 votes
candidate 3: 1 votes

Size decided at run time

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char line[32];
    size_t n = 0;
    printf("How many readings? ");
    if (!fgets(line, sizeof line, stdin) || sscanf(line, "%zu", &n) != 1 || n == 0) return 1;

    double *readings = malloc(n * sizeof *readings);
    if (readings == NULL) return 1;

    double sum = 0;
    for (size_t i = 0; i < n; i++) {
        if (!fgets(line, sizeof line, stdin) || sscanf(line, "%lf", &readings[i]) != 1) {
            readings[i] = 0;
        }
        sum += readings[i];
    }
    printf("\naverage of %zu readings: %.2f\n", n, sum / n);
    free(readings);
    return 0;
}
Input
4
12.5
14.0
11.75
13.25
Output
How many readings?
average of 4 readings: 12.88

Returning allocated memory

Memory from malloc outlives the function that allocated it, so a function can create something and hand it to its caller. The caller then owns it and must free it — document that clearly:

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Returns a new string "first last". The caller must free() it.
char *full_name(const char *first, const char *last) {
    size_t len = strlen(first) + 1 + strlen(last) + 1;
    char *result = malloc(len);
    if (result == NULL) return NULL;
    snprintf(result, len, "%s %s", first, last);
    return result;
}

int main(void) {
    char *name = full_name("Grace", "Hopper");
    if (name != NULL) {
        printf("%s (%zu characters)\n", name, strlen(name));
        free(name);
    }
    return 0;
}
Output
Grace Hopper (12 characters)

POSIX and C23 provide strdup, which allocates a copy of a string the same way.

Memory leaks

Memory you never free stays reserved until the program exits — a leak. A short program that exits soon won't notice, but a server or a long loop that leaks a little each time will eventually run out of memory. Every malloc needs exactly one matching free on every path through the code — which is where the goto cleanup pattern from lesson 20 helps.

Rules of thumb

  • Use the stack for small, fixed-size, short-lived data; the heap for large, variable-size or long-lived data.
  • Always check the result of malloc/calloc.
  • Decide who owns each allocation, and free it in one place.
  • Never use memory after freeing it, never free it twice, and only free pointers that came from malloc, calloc or realloc (lesson 38 shows these bugs and the tools that find them).
  • free(NULL) is safe and does nothing.

Try it yourself

Allocate an array for the first n Fibonacci numbers, fill it, print it, and free it.

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
Output
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377