C · Intermediate

Dynamic Memory in C

10 min readUpdated September 24, 2026Every example verified

In short: Dynamic memory is storage a program requests at run time with malloc, calloc or realloc when the size is not known when the program is written. It lives on the heap until the program hands it back with free, so it can outlive the function that created it. Every allocation must be checked for NULL, used only within its size, and freed exactly once.

Memory whose size is decided at run time

An array declared as int temps[100] has its size fixed when the program is compiled, and it disappears when the function that declared it returns. Two situations break that model: the amount of data is only known at run time (a count read from input, a file of unknown length), or the data must outlive the function that builds it. Dynamic allocation solves both. malloc(n) asks the C library for n bytes from a region called the heap and returns a pointer to them, or NULL if the request cannot be met. The memory stays yours until you call free on that pointer, however many functions return in between.

The idiom for an array of n elements is int *p = malloc(n * sizeof *p);. Writing sizeof *p rather than sizeof(int) ties the size to the pointer's type, so the line stays correct if the type changes. The result needs no cast in C, because void * converts to any object pointer automatically. Test for NULL immediately; a program that dereferences a failed allocation crashes far from the cause.

malloc leaves the bytes uninitialised. calloc(n, size) allocates n elements of size bytes and sets them all to zero, which is what counters and empty tables need. realloc(p, new_size) changes the size of an existing block, copying the contents to a new location if it has to; that is how a list grows as input arrives. Because realloc returns NULL on failure while leaving the old block intact, assign its result to a temporary first: p = realloc(p, ...) would overwrite your only pointer to the old block with NULL and leak it.

Heap memory has three rules. Use only the bytes you asked for: writing element n of an n-element block is undefined behaviour that typically corrupts the allocator's bookkeeping. Free every block exactly once: freeing twice, or freeing a pointer that did not come from an allocation function, is undefined behaviour, and never freeing is a leak. Do not touch a block after freeing it; setting the pointer to NULL after free turns a later slip into a clean crash instead of silent corruption.

A function may allocate and return memory, but the caller must know it owns the result: say so in a comment, as repeat_word below does. Valgrind and the compiler's AddressSanitizer (-fsanitize=address) report leaks, double frees and out-of-bounds writes, and are worth running on any program that uses the heap.

Syntax

 C · syntax
#include <stdlib.h>

int *p = malloc(n * sizeof *p);        /* n ints, contents undefined */
if (p == NULL) { /* handle the failure */ }

int *z = calloc(n, sizeof *z);         /* n ints, all zero */

int *bigger = realloc(p, 2 * n * sizeof *p);   /* grow; the block may move */
if (bigger != NULL) { p = bigger; }    /* replace p only on success */

free(p);                               /* give the block back */
p = NULL;                              /* optional: makes a later use fail loudly */

Sizes are in bytes and have type size_t. free(NULL) is allowed and does nothing, and realloc(NULL, n) behaves like malloc(n).

The allocation functions

FunctionDoesInitial contentsNotes
malloc(bytes)allocates one blockundefinedreturns NULL on failure
calloc(count, size)allocates count elements of size bytesall zeroglibc also rejects a count × size that overflows
realloc(p, bytes)resizes a block, moving it if neededold contents kept, up to the smaller sizereturns NULL on failure and leaves p valid
free(p)releases a block-p must come from one of the three above, once only

An array sized by the input

The first number says how many readings follow; the array is created to fit.

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

int main(void) {
    int n;
    if (scanf("%d", &n) != 1 || n <= 0) {
        printf("need a positive count\n");
        return 1;
    }

    int *temps = malloc(n * sizeof *temps);
    if (temps == NULL) {
        printf("out of memory\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        if (scanf("%d", &temps[i]) != 1) {
            free(temps);
            return 1;
        }
    }

    int lowest = temps[0], highest = temps[0];
    long sum = 0;
    for (int i = 0; i < n; i++) {
        sum += temps[i];
        if (temps[i] < lowest) lowest = temps[i];
        if (temps[i] > highest) highest = temps[i];
    }
    printf("%d readings: min %d, max %d, mean %.1f\n", n, lowest, highest, (double) sum / n);

    free(temps);
    return 0;
}

Input given to the program: 530 42 18 27 33

Output

5 readings: min 18, max 42, mean 30.0

The size of temps is decided only after n is read. malloc(n * sizeof *temps) requests exactly n ints, and the NULL check runs before any element is touched. If reading fails part-way, the early return frees the block first so that no path leaks. After the statistics are printed the block is released. This short program would behave the same without that free, since the operating system reclaims everything at exit, but the same omission inside a loop of a long-running program would exhaust memory.

Growing a list with realloc

Numbers are read until the input ends, into a block that doubles whenever it fills.

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

int main(void) {
    int capacity = 2;
    int count = 0;
    int *items = malloc(capacity * sizeof *items);
    if (items == NULL) {
        return 1;
    }
    printf("capacity %d\n", capacity);

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

    printf("read %d values:", count);
    for (int i = 0; i < count; i++) {
        printf(" %d", items[i]);
    }
    printf("\n");

    free(items);
    return 0;
}

Input given to the program: 7 3 9 4 12

Output

capacity 2
capacity 4
capacity 8
read 5 values: 7 3 9 4 12

The list starts with room for two values. Each time count reaches capacity, realloc asks for twice the space; the result goes into bigger, and only when it is not NULL does items take the new address. Doubling rather than adding one element keeps the number of reallocations logarithmic in the number of values, which matters because each realloc may copy the whole block. Count and capacity are tracked separately, because the block knows nothing about how much of it is in use.

Returning heap memory from a function

A function that builds a string of computed length, and a calloc for zeroed counters.

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

/* Builds "word-word-word" in fresh heap memory. The caller must free it. */
char *repeat_word(const char *word, int times) {
    size_t len = strlen(word);
    char *out = malloc(times * (len + 1));
    if (out == NULL) {
        return NULL;
    }
    char *p = out;
    for (int i = 0; i < times; i++) {
        memcpy(p, word, len);
        p += len;
        *p++ = (i < times - 1) ? '-' : '\0';
    }
    return out;
}

int *zeroed_counts(int n) {
    return calloc(n, sizeof(int));
}

int main(void) {
    char *chant = repeat_word("hop", 3);
    if (chant == NULL) {
        return 1;
    }
    printf("%s (%zu chars)\n", chant, strlen(chant));
    free(chant);

    int *counts = zeroed_counts(4);
    if (counts == NULL) {
        return 1;
    }
    counts[2] = 5;
    for (int i = 0; i < 4; i++) {
        printf("%d%c", counts[i], i < 3 ? ' ' : '\n');
    }
    free(counts);
    return 0;
}

Output

hop-hop-hop (11 chars)
0 0 5 0

repeat_word computes the exact size it needs, times copies of the word each followed by either a hyphen or the final terminator, allocates it, fills it with memcpy and hands the block to the caller. The comment above it states the ownership rule, and main honours it by calling free(chant) when done. zeroed_counts shows calloc doing the allocation and the zero-fill in one step: only counts[2] is assigned, yet every element prints a defined value. A local array in either function could not have been returned, because it would vanish with the function's stack frame, as the pointers lesson warns.

Common mistakes

  • Not checking the result of malloc

    Why it goes wrong: On failure malloc returns NULL, and writing through NULL crashes. Failures are rare on a desktop but routine on small devices or when a size is computed wrongly.

    Fix: Test every allocation immediately and handle the failure, even if handling means printing a message and exiting.

     C · fix
    int *p = malloc(n * sizeof *p);
    if (p == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
  • Overwriting the pointer with realloc's result

    Why it goes wrong: p = realloc(p, size) sets p to NULL when realloc fails, and the original block, which is still allocated, can no longer be freed.

    Fix: Store the result in a temporary and assign it to p only if it is not NULL.

     C · fix
    int *tmp = realloc(p, size);
    if (tmp == NULL) {
        /* p is still valid; report or keep going with the old size */
    } else {
        p = tmp;
    }
  • Using memory after freeing it, or freeing it twice

    Why it goes wrong: After free(p) the block may be handed to another allocation. Reading it returns unrelated data, writing corrupts it, and a second free(p) corrupts the allocator itself.

    Fix: Free a block in one place, set the pointer to NULL afterwards, and make sure no other pointer still refers to the block.

  • Allocating the size of the pointer instead of the data

    Why it goes wrong: char *s = malloc(sizeof s) allocates the size of a pointer, 8 bytes on a 64-bit system, not the text. malloc(strlen(text)) is one byte too small for the terminator.

    Fix: For an array of elements use n * sizeof *p; for a copy of a string use strlen(text) + 1.

Where you use this

Reading a file whose length you do not know is the classic case: read values into a growing block with the doubling pattern from the second example. The same pattern, wrapped in a struct that holds the pointer, the count and the capacity, is a reusable growable list. Linked structures, where each node is a separate malloc holding a pointer to the next, are the other major use: an undo history or a queue of pending jobs is built that way, and every node is freed when the structure is torn down.

 C · in practice
struct IntList {
    int *items;
    int count;
    int capacity;
};

int list_push(struct IntList *l, int value) {
    if (l->count == l->capacity) {
        int cap = l->capacity == 0 ? 4 : l->capacity * 2;
        int *tmp = realloc(l->items, cap * sizeof *tmp);
        if (tmp == NULL) return 0;
        l->items = tmp;
        l->capacity = cap;
    }
    l->items[l->count++] = value;
    return 1;
}

Key points

  • malloc, calloc and realloc return heap memory that lasts until free; check every result for NULL.
  • Size allocations with n * sizeof *p, and add 1 for a string's terminator.
  • calloc zeroes the block; malloc does not.
  • Assign realloc's result to a temporary so that a failure does not lose the old block.
  • Free each block exactly once and never use it afterwards.
  • A function may return heap memory; say clearly that the caller must free it.

Try it yourself

Read a count n and then n integers into a dynamically allocated array in which each element holds the running total of the values so far. Print the totals separated by spaces and free the array. For the input 4 followed by 5 8 2 9, print 5 13 15 24.

Your program
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    if (scanf("%d", &n) != 1 || n <= 0) {
        return 1;
    }

    /* 1. allocate an array of n ints called totals (check for NULL)
       2. read n values; store in totals[i] the sum of every value read so far
       3. print the totals separated by single spaces, then free the array */

    return 0;
}
Input the program receives: 4 ↵ 5 8 2 9
Expected output: 5 13 15 24

Practise this

Exercises for this lesson are in the C practice set.

Open the C playground

Frequently asked questions

Do I need to cast the result of malloc in C?

No. malloc returns void *, which C converts to any object pointer type without a cast, so int *p = malloc(n * sizeof *p); is complete. The cast is required in C++, which is where the habit comes from. In C it can even hide a bug: if <stdlib.h> is missing, older compilers assumed malloc returned int, and the cast silenced the warning that would have revealed it.

What is the difference between malloc and calloc?

Both allocate heap memory. calloc(count, size) takes the element count and size separately and fills the block with zero bytes; malloc(bytes) takes one size and leaves the contents undefined. Use calloc when zeroed memory is what you need, such as counters or a table of pointers that should start as NULL, and malloc when you are about to overwrite every byte anyway.

What happens if I never call free?

The memory stays allocated until the process exits, when the operating system reclaims all of it. For a short program that runs and finishes, the practical effect is nil. In a server, a long loop or a library, memory that is allocated repeatedly and never freed accumulates until the process runs out, so the habit of freeing what you allocate matters from the start.

Progress is stored only in this browser.

How this page was checked. Every program on it was run with GCC 13 (C11) at build time by the publishing checks, and the output shown is what it printed. Running C inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C11) locally.