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

  1. Allocate an array of ten ints with malloc, fill it with squares, print it and free it.
  2. Grow an array from three to six elements with realloc and keep the existing values; print the array after the move.
  3. 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.

  1. How should the size passed to malloc be written for an array?

    Answer

    count * sizeof *pointer

    Scaling by the size of the pointed-to object keeps the expression correct when the element type changes.

  2. How does calloc differ from malloc?

    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.

  3. Why is realloc assigned to a temporary pointer first?

    Answer

    Because the old block must not be lost if the call fails

    On failure realloc returns NULL and leaves the old block valid, so overwriting the only pointer to it would leak the block.

  4. What does malloc return when it cannot satisfy the request?

    Answer

    NULL

    A failed allocation returns NULL, and every result is checked before it is used.

  5. 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.

  6. 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

    calloc zeroes every byte, so all four ints start at zero.

  7. 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.

  8. 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_t cannot be satisfied, so malloc returns NULL and the program prints the failure branch.

  9. 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

    malloc and free are undeclared without <stdlib.h>, so the error is an implicit declaration.

  10. 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