malloc, calloc and realloc
The heap from lesson 30 is managed with three functions: malloc reserves bytes, realloc changes the size of an existing block and free returns it. All three live in stdlib.h, and every successful allocation needs exactly one free.
malloc and free
malloc takes a size in bytes and returns void *, which converts to any object pointer without a cast in C. The size is best written as a count times sizeof *pointer, so a later change of the element type updates it automatically. The result can be NULL and must be checked.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *values = malloc(3 * sizeof *values);
if (values == NULL) {
return 1;
}
values[0] = 10;
values[1] = 20;
values[2] = 30;
printf("%d %d %d\n", values[0], values[1], values[2]);
free(values);
return 0;
}
gcc -Wall -Wextra malloc.c -o malloc
./malloc
10 20 30
The block is uninitialised, exactly like an automatic array, so every element is written before it is read. After free the pointer still holds the old address, but using it is undefined; a good habit is to assign NULL right after the free.
calloc zeroes
calloc takes a count and an element size instead of one byte count, and it zeroes the block. That is one call fewer than malloc plus memset, and the zero is the right initial value for counters, sums and arrays of pointers. The result is still checked for NULL.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *values = calloc(4, sizeof *values);
if (values == NULL) {
return 1;
}
for (int i = 0; i < 4; i++) {
printf("%d ", values[i]);
}
printf("\n");
free(values);
return 0;
}
gcc -Wall -Wextra calloc.c -o calloc
./calloc
0 0 0 0
The multiplication count * size happens inside calloc and is checked for overflow, which is a small safety advantage over writing the product by hand. The allocation still costs one free at the end.
Growing with realloc
realloc takes an existing block and a new size. It may return the same address or move the data to a larger block; either way the old pointer must not be used if the call succeeded elsewhere. The result therefore goes into a temporary pointer, which is only assigned back after the NULL check.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *values = malloc(2 * sizeof *values);
if (values == NULL) {
return 1;
}
values[0] = 1;
values[1] = 2;
int *larger = realloc(values, 5 * sizeof *values);
if (larger == NULL) {
free(values);
return 1;
}
values = larger;
for (int i = 2; i < 5; i++) {
values[i] = (i + 1) * 10;
}
for (int i = 0; i < 5; i++) {
printf("%d ", values[i]);
}
printf("\n");
free(values);
return 0;
}
gcc -Wall -Wextra grow.c -o grow
./grow
1 2 30 40 50
The first two values survived the move, and the three new elements were written after the growth. The elements are still in the same order; only the address of the block may have changed. The part on data structures turns this pattern into a reusable growable array.
Allocation can fail
A request larger than the address space cannot succeed, and the function returns NULL without touching the program. The example asks for half of the largest size_t, which is far beyond any machine, and prints the branch it took.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t size = (size_t) -1 / 2;
void *block = malloc(size);
printf("%s\n", block == NULL ? "allocation failed" : "allocated");
free(block);
return 0;
}
gcc -Wall -Wextra huge.c -o huge
./huge
allocation failed
free(NULL) is a no-op, so the failure path needs no second branch. A server that must not die on a failed allocation retries, shrinks the request or reports the error to its caller instead of continuing with a null pointer.
No header, double free
Without stdlib.h the compiler does not know the functions. In C23 an undeclared function is an error, and the note names the header that fixes both calls:
#include <stdio.h>
int main(void)
{
int *values = malloc(3 * sizeof *values);
if (values == NULL) {
return 1;
}
values[0] = 1;
printf("%d\n", values[0]);
free(values);
return 0;
}
gcc -Wall -Wextra noinclude.c -o noinclude
noinclude.c: In function 'main':
noinclude.c:5:19: error: implicit declaration of function 'malloc' [-Wimplicit-function-declaration]
5 | int *values = malloc(3 * sizeof *values);
| ^~~~~~
noinclude.c:2:1: note: include '<stdlib.h>' or provide a declaration of 'malloc'
1 | #include <stdio.h>
+++ |+#include <stdlib.h>
2 |
noinclude.c:5:19: warning: incompatible implicit declaration of built-in function 'malloc' [-Wbuiltin-declaration-mismatch]
5 | int *values = malloc(3 * sizeof *values);
| ^~~~~~
noinclude.c:12:5: error: implicit declaration of function 'free' [-Wimplicit-function-declaration]
12 | free(values);
| ^~~~
noinclude.c:12:5: note: include '<stdlib.h>' or provide a declaration of 'free'
noinclude.c:12:5: warning: incompatible implicit declaration of built-in function 'free' [-Wbuiltin-declaration-mismatch]
noinclude.c:12:5: note: include '<stdlib.h>' or provide a declaration of 'free'
The second mistake frees the same block twice. GCC sees the second call and warns that the pointer is used after the free, and the C library aborts the process when the call actually runs:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *values = malloc(sizeof *values);
if (values == NULL) {
return 1;
}
*values = 3;
free(values);
free(values);
printf("done\n");
return 0;
}
gcc -Wall -Wextra doublefree.c -o doublefree
./doublefree; echo "exit: $?"
doublefree.c: In function 'main':
doublefree.c:13:5: warning: pointer 'values' used after 'free' [-Wuse-after-free]
13 | free(values);
| ^~~~~~~~~~~~
doublefree.c:12:5: note: call to 'free' here
12 | free(values);
| ^~~~~~~~~~~~
exit: 139
The message is the same for any use after free, including a read or a write through the old pointer. Setting the pointer to NULL after the first free turns the second call into the harmless free(NULL) and any later use into a null dereference that is easier to find.
Exercises
- Allocate an array of ten ints with malloc, fill it with squares, print it and free it.
- Grow an array from three to six elements with realloc and keep the existing values; print the array after the move.
- Write a function that takes a count and returns a zeroed array of that many doubles, or NULL when the allocation fails.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
How should the size passed to
mallocbe written for an array?Answer
count * sizeof *pointerScaling by the size of the pointed-to object keeps the expression correct when the element type changes.
-
How does
callocdiffer frommalloc?Answer
It zeroes the block it returns
calloc(count, size)multiplies the two values internally, checks the product and fills the block with zero bytes. -
Why is
reallocassigned to a temporary pointer first?Answer
Because the old block must not be lost if the call fails
On failure
reallocreturnsNULLand leaves the old block valid, so overwriting the only pointer to it would leak the block. -
What does
mallocreturn when it cannot satisfy the request?Answer
NULLA failed allocation returns
NULL, and every result is checked before it is used. -
What does this program print?
#include <stdio.h> #include <stdlib.h> int main(void) { int *values = malloc(3 * sizeof *values); if (values == NULL) return 1; values[0] = 10; values[1] = 20; values[2] = 30; printf("%d %d %d\n", values[0], values[1], values[2]); free(values); return 0; }Answer
10 20 30
The three elements are written after the allocation and printed in order before the free.
-
What does this program print?
#include <stdio.h> #include <stdlib.h> int main(void) { int *values = calloc(4, sizeof *values); if (values == NULL) return 1; for (int i = 0; i < 4; i++) printf("%d ", values[i]); printf("\n"); free(values); return 0; }Answer
0 0 0 0
calloczeroes every byte, so all four ints start at zero. -
What does this program print?
#include <stdio.h> #include <stdlib.h> int main(void) { int *values = malloc(2 * sizeof *values); if (values == NULL) return 1; values[0] = 1; values[1] = 2; int *larger = realloc(values, 5 * sizeof *values); if (larger == NULL) { free(values); return 1; } values = larger; for (int i = 2; i < 5; i++) values[i] = (i + 1) * 10; for (int i = 0; i < 5; i++) printf("%d ", values[i]); printf("\n"); free(values); return 0; }Answer
1 2 30 40 50
The first two values survive the growth, and the new elements are filled with 30, 40 and 50.
-
What does this program print?
#include <stdio.h> #include <stdlib.h> int main(void) { size_t size = (size_t) -1 / 2; void *block = malloc(size); printf("%s\n", block == NULL ? "allocation failed" : "allocated"); free(block); return 0; }Answer
allocation failed
Half of the largest
size_tcannot be satisfied, somallocreturnsNULLand the program prints the failure branch. -
This program is built with
gcc -std=c17 -Wall -Wextra noinclude.c -o noinclude. Which error does GCC print?#include <stdio.h> int main(void) { int *values = malloc(3 * sizeof *values); if (values == NULL) return 1; values[0] = 1; printf("%d\n", values[0]); free(values); return 0; }Answer
implicit declaration of function
mallocandfreeare undeclared without<stdlib.h>, so the error is an implicit declaration. -
This program is built with
gcc -std=c17 -Wall -Wextra doublefree.c -o doublefree. What does GCC warn about?#include <stdio.h> #include <stdlib.h> int main(void) { int *values = malloc(sizeof *values); if (values == NULL) return 1; *values = 3; free(values); free(values); printf("done\n"); return 0; }Answer
pointer 'values' used after 'free' [-Wuse-after-free]
GCC points at the second free and at the call that released the block.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev