Pointers & Memory · intermediate · ~6 min
By the end of this lesson you will be able to: - Allocate heap memory that is **already filled with zeros** in a single call using `calloc`. - Read and write the two-argument signature `calloc(count, element_size)` correctly, and explain why it differs from `malloc`. - Explain how `calloc` protects you from **multiplication overflow** when sizing an array. - Decide when `calloc` is the right tool and when `malloc` is the better, faster choice. - Check the return value, use the memory safely, and `free` it exactly once. - Understand what "all bits zero" really means for integers, pointers, and floating-point types.
In the prerequisite lesson malloc and the heap, you learned to ask the operating system for a block of heap memory at run time with malloc. There is one catch with malloc: the bytes it hands back are uninitialised. They contain whatever leftover values happened to be in that region of memory before — often called garbage. Reading those bytes before writing your own values is undefined behaviour and a classic source of bugs.
calloc (think "cleared allocation") solves a very common case: you want a fresh block of memory that starts out completely empty. Instead of calling malloc and then zeroing the block yourself with memset, calloc does both steps in one call. It allocates the memory and guarantees every byte is set to 0 before it returns to you.
calloc lives in <stdlib.h>, the same standard header as malloc, realloc, and free. It is one of the four core dynamic-memory functions in C, and it is used constantly in real programs: any time you need an array of counters that start at zero, a table of flags that start "off", or a struct that should begin in a clean state.
The second thing calloc gives you — which beginners often miss — is a safety feature in its signature. Because you pass the element count and the element size as two separate arguments, calloc can detect when multiplying them would overflow and refuse the allocation, returning NULL instead of silently handing back a too-small buffer. This makes calloc a recommended choice when the size comes from user input or another untrusted source.
Reading uninitialised memory is one of the most common and most dangerous mistakes in C. A counter that should start at 0 but instead starts at some random garbage value will produce wrong results that are maddeningly hard to reproduce, because the garbage changes from run to run. calloc removes that entire class of bug for the common "start empty" case.
The overflow protection matters in real software, especially anything that takes a size from outside the program — a file header, a network packet, a form field. If an attacker (or just a bad input) can make n * sz wrap around to a small number, a malloc(n * sz) call would allocate a tiny buffer while the rest of the program believes it is huge. The subsequent writes run off the end of the buffer — a heap buffer overflow, a vulnerability that has powered countless real-world exploits. Because calloc checks the multiplication for you, it closes that door automatically. Using the right allocation function is a small habit with an outsized payoff for correctness and security.
calloc does, step by stepcalloc(n, sz) performs three actions:
n * sz, checking that the multiplication does not overflow.0, then returns a pointer to the first byte.If the multiplication overflows, or the OS cannot satisfy the request, calloc returns NULL. You must always check for that.
calloc(4, sizeof(int)) -> reserve 4 * 4 = 16 bytes, all zeroed
returned pointer
|
v
+----+----+----+----+----+----+----+----+ ... (16 bytes total)
| 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
+----+----+----+----+----+----+----+----+
\_______ int[0] _______/ \____ int[1] ...
= 0 = 0
Knowledge check: If malloc(16) and calloc(4, sizeof(int)) reserve the same number of bytes on a system where int is 4 bytes, what is the one guaranteed difference between the two blocks right after the call?
malloc takes one argument (the total number of bytes). calloc takes two: the number of elements and the size of each element.
void *malloc(size_t total_bytes);
void *calloc(size_t count, size_t element_size);
This is not just a stylistic difference. By keeping count and element_size separate, calloc can verify that count * element_size fits in a size_t before allocating. If it would overflow, calloc returns NULL rather than wrapping around to a small value. Writing the multiplication yourself with malloc(count * element_size) gives up that check.
Attacker / bad input supplies a huge count
|
v
malloc(count * sz) calloc(count, sz)
----------------- -----------------
count * sz computed by YOU count * sz computed by calloc
overflow -> small number overflow detected
tiny buffer returned NULL returned
later writes overflow heap X allocation safely refused OK
Knowledge check: Why can calloc detect the overflow but a plain malloc(count * element_size) cannot? (Hint: think about who computes the product and when.)
calloc guarantees every byte is 0. For unsigned and signed integers, all-bits-zero is exactly the integer value 0, so calloc'd ints, chars, and size_ts reliably start at 0. That covers the vast majority of real uses.
The subtle part: the C standard does not guarantee that all-bits-zero equals the value 0.0 for floating-point types, nor that it equals a valid null pointer on every conceivable platform. On the normal machines you will use (which use IEEE-754 floats and an all-zero null pointer), 0.0 and NULL do come out correctly. But the portable, standard-blessed way to get a zero of those types is still plain assignment (x = 0.0; p = NULL;). For integer data — counters, flags, byte buffers — calloc is perfectly safe and idiomatic.
Knowledge check: You calloc an array of int and immediately print element [3]. What value is guaranteed to print, and why is that guarantee absent for malloc?
The zero-fill is extra work, so calloc can be slower than malloc — but often the difference is tiny, because the OS frequently hands the program memory pages that are already zeroed for security reasons, and calloc can skip re-zeroing those.
Do not use calloc when you are going to immediately overwrite every byte anyway (for example, copying a whole file into the buffer right after allocating). In that case the zero-fill is pure waste; use malloc. Use calloc when the memory should genuinely start empty.
Common pitfall: treating calloc and malloc as interchangeable in their argument shape. Calling calloc(10 * sizeof(int)) with a single argument is a compile error (too few arguments); calling malloc(10, sizeof(int)) with two is also an error. Each function has its own fixed shape.
#include <stdlib.h> /* declares calloc and free */
/* void *calloc(size_t count, size_t element_size); */
int *a = calloc(10, sizeof(int)); /* 10 ints, all set to 0 */
if (a == NULL) { /* ALWAYS check the result */
/* allocation failed (out of memory or overflow) */
}
/* use a[0] .. a[9] freely; they are all 0 to begin with */
free(a); /* release the block exactly once when done */
a = NULL; /* optional: avoid a dangling pointer */
Key points annotated:
void *; in C it converts to your pointer type automatically (no cast needed).sizeof(int) makes the size portable — never hard-code 4.calloc do the multiplication.calloc must be matched by exactly one free.calloc doesvoid *calloc(size_t n, size_t sz);
calloc allocates space for n items, each sz bytes in size. It reserves n * sz bytes in total and fills them all with zeros before returning.
Reach for calloc when you want memory that starts out empty:
0.calloc is slightly slower than malloc because of the zero-fill step. In practice the cost is often small: the operating system may hand back pages that are already zeroed.
Note that calloc takes two arguments, not one:
calloc(10, sizeof(int)); /* correct */
calloc(10 * sizeof(int)); /* wrong: that is malloc's shape */
Keeping the count and the element size separate has a safety benefit. Some compilers can detect multiplication overflow (when n * sz is too large to represent) when you use this two-argument form.
#include <stdio.h>
#include <stdlib.h>
/*
* Build a frequency table of digit characters ('0'..'9') in a string.
* calloc is ideal here: every counter must START at zero.
*/
int main(void) {
const char *text = "phone: 555-0142, pin: 0007";
/* 10 counters, one per digit, all initialised to 0 by calloc */
int *counts = calloc(10, sizeof(int));
if (counts == NULL) { /* allocation may fail */
fprintf(stderr, "calloc failed\n");
return 1;
}
for (const char *p = text; *p != '\0'; p++) {
if (*p >= '0' && *p <= '9') {
counts[*p - '0']++; /* no manual zeroing needed */
}
}
for (int d = 0; d < 10; d++) {
printf("digit %d appeared %d time(s)\n", d, counts[d]);
}
free(counts); /* release the heap block */
counts = NULL; /* avoid a dangling pointer */
return 0;
}
What it does: It allocates ten int counters with calloc, so each starts at 0 with no manual initialisation. It then scans the string, and for each digit character it increments the matching counter. Finally it prints how many times each digit 0–9 appeared and frees the memory.
Expected output:
digit 0 appeared 4 time(s)
digit 1 appeared 2 time(s)
digit 2 appeared 1 time(s)
digit 3 appeared 0 time(s)
digit 4 appeared 2 time(s)
digit 5 appeared 4 time(s)
digit 6 appeared 0 time(s)
digit 7 appeared 1 time(s)
digit 8 appeared 0 time(s)
digit 9 appeared 0 time(s)
Edge cases: If text were empty, every count would print as 0 (thanks to calloc). If calloc returned NULL, the program prints an error and exits with status 1 instead of dereferencing a null pointer. The same program written with malloc would print garbage counts unless you also called memset(counts, 0, 10 * sizeof(int)).
int *counts = calloc(10, sizeof(int)); — calloc checks 10 * sizeof(int) for overflow, reserves that many bytes (40 on a typical system), zeroes them all, and returns the address of the first byte into counts.if (counts == NULL) — if the allocation failed, print an error and return 1. Skipping this check risks a crash when memory is tight or the size overflows.for loop walks the string one character at a time via the pointer p until it reaches the terminating '\0'.if (*p >= '0' && *p <= '9') — only digit characters are counted; punctuation and letters are ignored.counts[*p - '0']++ — the expression *p - '0' converts a digit character to its numeric index ('5' - '0' is 5), and ++ increments that counter. This works correctly only because every counter started at 0.A short trace as the loop meets the first few interesting characters of "phone: 555-0142...":
char is digit? index counts after
'p' no - [0 0 0 0 0 0 0 0 0 0]
':' no - [0 0 0 0 0 0 0 0 0 0]
'5' yes 5 [0 0 0 0 0 1 0 0 0 0]
'5' yes 5 [0 0 0 0 0 2 0 0 0 0]
'5' yes 5 [0 0 0 0 0 3 0 0 0 0]
'0' yes 0 [1 0 0 0 0 3 0 0 0 0]
'1' yes 1 [1 1 0 0 0 3 0 0 0 0]
'4' yes 4 [1 1 0 0 1 3 0 0 0 0]
'2' yes 2 [1 1 1 0 1 3 0 0 0 0]
free(counts) returns the 40-byte block to the heap; counts = NULL prevents accidental reuse of a freed pointer.malloc argument shape with calloc/* WRONG: calloc takes TWO arguments */
int *a = calloc(10 * sizeof(int)); /* compile error: too few args */
Why it is wrong: calloc's signature is calloc(count, element_size). Collapsing it into one argument both fails to compile and throws away the overflow protection that is the whole point of the two-argument form.
/* CORRECT */
int *a = calloc(10, sizeof(int));
How to prevent it: remember malloc = total bytes (one arg), calloc = count then size (two args).
malloc also zeroes/* WRONG: malloc leaves the memory uninitialised */
int *c = malloc(10 * sizeof(int));
printf("%d\n", c[0]); /* prints garbage; undefined behaviour */
Why it is wrong: only calloc guarantees zeros. Reading malloc'd memory before writing it is undefined behaviour.
/* CORRECT: use calloc when you need zeros */
int *c = calloc(10, sizeof(int));
printf("%d\n", c[0]); /* prints 0, guaranteed */
How to recognise it: counters or flags that hold wild values that change between runs are a tell-tale sign of uninitialised memory.
/* WRONG */
int *a = calloc(huge, sizeof(int));
a[0] = 1; /* crash if calloc returned NULL */
Why it is wrong: calloc returns NULL on failure or on detected overflow. Dereferencing NULL crashes (or worse).
/* CORRECT */
int *a = calloc(huge, sizeof(int));
if (a == NULL) { /* handle failure */ return 1; }
a[0] = 1;
/* WASTEFUL: calloc already zeroed it */
int *a = calloc(n, sizeof(int));
memset(a, 0, n * sizeof(int)); /* redundant work */
Why it is wrong: not a bug, but pointless effort. If you need zeros, calloc alone is enough; if you are about to overwrite everything, use malloc and skip both.
Compiler errors
malloc shape. Split into calloc(count, size).#include <stdlib.h>.(int *) cast — harmless but unidiomatic.Runtime errors
NULL, or you indexed past the end of the array (counts[10] when only counts[0..9] exist).free — you freed the same pointer twice, or freed a pointer that calloc did not return (for example, one you had already advanced with p++). Keep the original pointer for free.Logic errors
malloc instead of calloc and forgot to zero. Switch to calloc and re-run.Debugging steps
gcc -Wall -Wextra -g program.c.valgrind ./a.out reports leaks, invalid reads/writes, and double frees with line numbers.0 .. count-1? Is there exactly one free per successful allocation?calloc is a tool that prevents one class of bug (uninitialised reads) but it does not make your code immune to the others. Keep these in mind for this topic:
calloc(n, sz) gives you valid indices 0 through n-1 only. Writing a[n] or beyond is a heap buffer overflow — undefined behaviour. The zero-fill does not change the size of the block.calloc(count, size) over malloc(count * size) whenever count comes from input, precisely so a giant count cannot wrap to a small allocation. This is calloc's built-in defence against heap-overflow exploits.free it. Using it after free is a use-after-free; freeing it twice is a double-free. Both are serious, exploitable bugs. Setting the pointer to NULL after free turns an accidental reuse into a clean crash instead.calloc must be freed on every path out of the function, including error paths. A path that returns early without freeing leaks the block.calloc to zero integer data and byte buffers. For double/float and pointer members where you want strict portability, assign 0.0 / NULL explicitly rather than depending on all-bits-zero. On mainstream platforms they coincide, but being explicit documents intent and is fully portable.Concrete uses
calloc'd so every slot starts empty (NULL/0) without a manual loop.calloc'd frame buffer is solid black / silence, a convenient and well-defined starting state.calloc(count, size) is preferred specifically for its overflow check.Professional best-practice habits
Beginner rules:
calloc when memory should start empty; use malloc when you will fill it immediately.NULL before using it.calloc with exactly one free; set the pointer to NULL afterwards.sizeof(type), never a hard-coded number.Advanced rules:
calloc(count, size) to malloc(count * size) for any size derived from input, to get free overflow detection.valgrind or compile with AddressSanitizer (-fsanitize=address) during development to catch leaks, overflows, and use-after-free early.goto cleanup;) in functions with multiple error returns to keep cleanup in one place.Allocate an array of 5 ints with calloc, print all five elements (they should all be 0), then free the array.
Requirements: check the return value; use sizeof(int); one free.
Expected output: 0 0 0 0 0.
Concepts: basic calloc call, NULL check, cleanup.
Allocate one array with malloc and one with calloc, both of 5 ints, and print both before writing anything. Note in a comment which one is guaranteed to be all zeros and why.
Requirements: do not write to either array before printing; free both.
Hint: the malloc output may differ between runs — that is the point.
Concepts: uninitialised vs zeroed memory.
Read a line of text with fgets, then use a calloc'd array of 10 counters to count how many times each digit 0–9 appears. Print the histogram and free the memory.
Input example: 2026-06-28
Output example: counts showing 0→2, 2→3, 6→2, 8→1, others 0.
Concepts: calloc for counters, character-to-index mapping, cleanup.
Write int *make_zeros(size_t count) that returns a calloc'd array of count ints, or NULL on failure. In main, call it with a normal value and a deliberately enormous value, and report which call succeeded. Free any non-NULL result.
Requirements: rely on calloc's overflow check; do not multiply yourself.
Concepts: overflow protection, returning ownership, NULL handling.
Implement a tiny dynamic array of int that starts empty and, when full, grows. Use calloc for the initial buffer so all slots read as 0, and explain in a comment why you must zero the new portion when you later grow it (hint: realloc does not zero, so the new tail is uninitialised). Provide push(value) and a function to print and free everything.
Requirements: no leaks, no out-of-bounds writes, check every allocation.
Concepts: calloc semantics, the gap realloc leaves, manual zeroing of grown memory, full cleanup.
(Do not look up a full solution first — design the growth step yourself.)
calloc(n, sz) reserves n * sz bytes and sets every byte to 0, in one call — ideal when memory should start empty.count, element_size) lets it detect multiplication overflow and return NULL instead of a too-small buffer; malloc(n * sz) cannot.0 for integers and byte data; for double/float and pointers, assign 0.0/NULL explicitly for strict portability.calloc for counters, flags, and clean structs; use malloc when you will overwrite every byte immediately (the zero-fill would be wasted).NULL, stay within indices 0 .. n-1, and free exactly once (then set the pointer to NULL).malloc zeroes, skipping the NULL check, and forgetting to free on error paths.