malloc, calloc, realloc and free in C: the differences

malloc, calloc, realloc and free: what each one does, the difference between malloc and calloc, and the traps of resizing and freeing memory in C.

By
Published on

All four functions come with a single #include <stdlib.h>, and yet they account for most of the memory bugs a C beginner writes. Knowing which one to call, and above all how not to lose a block along the way, changes everything.

In short

malloc(size) reserves a block on the heap without initializing it. calloc(count, size) does the same, but zeroes everything and checks that count * size doesn't overflow. realloc(p, size) resizes an existing block. free(p) gives it back. Every allocated block gets exactly one free. Always check the return value against NULL, and never write p = realloc(p, n) directly, because a failure would lose you the old block.

Which one to use, in one table

All four live in <stdlib.h> and all four work on the heap, the area where a block outlives the function that created it (how it differs from the stack is covered in stack vs heap in C).

FunctionRoleInitializes?Checks for overflow?
malloc(size)reserves size bytesno, contents indeterminateno
calloc(nmemb, size)reserves nmemb * size bytesyes, all zeroyes
realloc(ptr, size)resizes a blockkeeps the old contentsno
free(ptr)gives a block backnot applicablenot applicable

Each function's entry gives its exact signature and return value: malloc(3), calloc(3), realloc(3), free(3).

malloc vs calloc: what's the difference?

The first difference is visible to the naked eye. malloc hands you a block as it is, with whatever was lying around in memory; calloc hands it to you zeroed. This small program prints the first 24 bytes of each, right after freeing a string to dirty the heap:

init.c
#include <stdio.h>#include <stdlib.h>#include <string.h>
static void dump(const char *label, const unsigned char *p, size_t n){    printf("%-7s", label);    for (size_t i = 0; i < n; i++)        printf(" %02x", p[i]);    printf("\n");}
int main(void){    char *old = malloc(24);    strcpy(old, "my password is secret");    free(old);
    unsigned char *m = malloc(24);    dump("malloc", m, 24);    unsigned char *c = calloc(24, 1);    dump("calloc", c, 24);    free(m);    free(c);    return 0;}
Terminal
$ gcc -Wall -Wextra -g init.c -o init$ ./initmalloc  d4 a8 03 00 00 00 00 00 00 00 00 00 00 00 00 00 65 63 72 65 74 00 00 00calloc  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

The malloc block holds leftovers (65 63 72 65 74, the letters ecret from "secret"), the calloc block is clean. If your code reads a byte before writing it, malloc gives you garbage and calloc a predictable zero.

The second difference can't be seen, and it's more serious. calloc takes two dimensions and refuses the allocation if their product overflows size_t, whereas with malloc(nmemb * size) you compute that product yourself. A product that overflows gives a small number:

overflow.c
#include <stdint.h>#include <stdio.h>#include <stdlib.h>
int main(void){    size_t n = SIZE_MAX / sizeof(int) + 2;
    int *a = malloc(n * sizeof(int));    int *b = calloc(n, sizeof(int));
    printf("n * sizeof(int) = %zu\n", n * sizeof(int));    printf("malloc          : %p\n", (void *)a);    printf("calloc          : %p\n", (void *)b);    free(a);    free(b);    return 0;}
Terminal
$ gcc -Wall -Wextra -g overflow.c -o overflowoverflow.c: In function 'main':overflow.c:10:14: warning: product '4611686018427387905 * 4' of arguments 1 and 2 exceeds 'SIZE_MAX' [-Walloc-size-larger-than=]   10 |     int *b = calloc(n, sizeof(int));      |              ^~~~~~~~~~~~~~~~~~~~~~...$ ./overflown * sizeof(int) = 4malloc          : 0x3c96f2a0calloc          : (nil)

n * sizeof(int) wrapped around to 4. So malloc reserved 4 bytes while believing it was answering a gigantic request, and any access past them overflows the block. calloc saw the overflow and returned NULL. That's the very wording of C23, which says calloc returns a pointer "or a null pointer if the space cannot be allocated or if the product nmemb * size would wraparound size_t". As soon as one of the dimensions comes from outside your program, calloc is the right reflex.

realloc: growing a block without losing it

realloc changes the size of a block that is already allocated. It keeps the contents up to the smaller of the two sizes, and it can either grow the block in place, or allocate somewhere else, copy, and free the old block. In that second case, the address changes.

Before realloc, 4 ints allocated

Stack

main()
int *scores
old

Heap

oldint[4]malloc(4 * sizeof(int))
10111213
After realloc to 8 ints, block moved

Stack

main()
int *scores
new

Heap

oldint[4]freed by reallocfreed
newint[8]the first 4 copied over
10111213????

The four original values are all there, and the four new slots hold indeterminate values. C23 spells it out: "Any bytes in the new object beyond the size of the old object have unspecified values."

The deadly trap is writing the result into the pointer you passed in. If realloc fails, it returns NULL without touching the old block: "the original block is left untouched; it is not freed or moved". But if you wrote p = realloc(p, n), you've just overwritten the only pointer to a block that is still allocated, and it's lost for good.

Leaks if realloc failsAvoid
p = realloc(p, n);if (p == NULL)    return 1;        /* the old block is unreachable */
Block keptCorrect
tmp = realloc(p, n);if (tmp == NULL) {    free(p);         /* p is still valid */    return 1;}p = tmp;

You can check this under Valgrind. The version that reassigns directly loses its bytes when the huge realloc fails, the version with a temporary loses nothing:

Terminal
$ valgrind -q --leak-check=full ./growout of memory, the 4 scores are lost==8== 16 bytes in 1 blocks are definitely lost in loss record 1 of 1==8==    at 0x488545C: malloc (vg_replace_malloc.c:446)==8==    by 0x400713: main (grow.c:7)==8== $ valgrind -q --leak-check=full ./grow-okout of memory, still holding the 4 scores: 13

Two shortcuts are built into the function. realloc(NULL, size) behaves like malloc(size). And realloc(p, 0), which freed the block up to C17, became undefined behavior in C23: stop using it to free anything, write free(p).

free: once, and only once

free gives a block back to the heap. The rule fits in one sentence: every address returned by malloc, calloc or realloc is passed to free exactly once, unchanged. One case stands out, and it's a convenient one: free(NULL) does nothing, and the standard guarantees it. So you can initialize your pointers to NULL and free them without a check.

Everything else is undefined behavior: freeing the same block twice, freeing a pointer you've moved (free(p + 1)), freeing a stack variable or a string literal. On a glibc system, that shows up as a message such as free(): double free detected in tcache 2 followed by an abrupt stop, decoded in double free detected in tcache.

One simple habit avoids most of these cases: after free(p), write p = NULL;. A second free then does nothing, and an accidental access crashes outright instead of reading a recycled area.

C

calloc

Write calloc yourself: first check for overflow with nmemb > SIZE_MAX / size, return NULL instead of letting the product wrap around, then allocate and set every byte to zero. That's exactly the guarantee malloc doesn't give you.

Open the exercise
C

mem_resize

The real two-argument realloc can't be rewritten in C, because there's no way to ask for the size of an existing block. The exercise gives you that size: move a block to a buffer of a new size while keeping its first bytes, then free the old one, in the right order.

Open the exercise

To put these functions to work on real cases, from copying a string to a homemade allocator, the C path has a module that brings them together:

Dynamic memory
8 exercises

Frequently asked questions

What is malloc actually for?

For asking for memory whose size you only know at run time, or that has to outlive the function that creates it. A local variable lives on the stack and disappears at the end of its block; a malloc block lives until your free, wherever you are in the program.

malloc or calloc: which one should I pick?

calloc whenever you want a zeroed area, or whenever one of the two dimensions (nmemb, size) comes from outside input, because it refuses a product that overflows. malloc when you're about to write every byte yourself right afterwards and the sizes are safe.

Do I need to check what malloc returns against NULL?

Yes, every time. A malloc that fails returns NULL, and writing through a NULL pointer causes a segmentation fault. The check costs one line.

Why not write p = realloc(p, n)?

Because if realloc fails, it returns NULL without freeing the old block. By reassigning p, you lose the only pointer to that block while it's still allocated: that's a leak. Go through a temporary, and only reassign p when the call succeeded.

Is free(NULL) dangerous?

No, it's explicitly defined to do nothing. That lets you free a pointer without checking whether it's null, and makes free(p); p = NULL; safe on a second pass.

Sources

Now write it yourself.