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.
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).
| Function | Role | Initializes? | Checks for overflow? |
|---|---|---|---|
malloc(size) | reserves size bytes | no, contents indeterminate | no |
calloc(nmemb, size) | reserves nmemb * size bytes | yes, all zero | yes |
realloc(ptr, size) | resizes a block | keeps the old contents | no |
free(ptr) | gives a block back | not applicable | not 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:
#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;}$ 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 00The 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:
#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;}$ 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.
Stack
- int *scores
- old
Heap
Stack
- int *scores
- new
Heap
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.
p = realloc(p, n);if (p == NULL) return 1; /* the old block is unreachable */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:
$ 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: 13Two 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.
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.
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.
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:
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
