Pointers & Memory · intermediate · ~6 min

calloc — zero-initialized allocation

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.

Overview

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.

Why it matters

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.

Core concepts

1. What calloc does, step by step

calloc(n, sz) performs three actions:

  1. Computes the total size n * sz, checking that the multiplication does not overflow.
  2. Requests that many bytes of heap memory.
  3. Sets every byte in the block to 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?

2. The two-argument signature — and why it exists

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

3. "All bits zero" vs. "the value zero"

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?

4. Cost, and when NOT to use it

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.

Syntax notes

#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:

  • The return type is void *; in C it converts to your pointer type automatically (no cast needed).
  • sizeof(int) makes the size portable — never hard-code 4.
  • The two arguments stay separate; let calloc do the multiplication.
  • Every successful calloc must be matched by exactly one free.

Lesson

What calloc does

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

When to use it

Reach for calloc when you want memory that starts out empty:

  • Counters that should begin at 0.
  • Flags that should begin as "off".
  • Structs that should start in a clean, zeroed state.

Cost

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.

The two-argument form

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.

Code examples

#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 09 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)).

Line by line

  1. 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.
  2. 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.
  3. The for loop walks the string one character at a time via the pointer p until it reaches the terminating '\0'.
  4. if (*p >= '0' && *p <= '9') — only digit characters are counted; punctuation and letters are ignored.
  5. 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]
  1. The reporting loop prints each digit and its count.
  2. free(counts) returns the 40-byte block to the heap; counts = NULL prevents accidental reuse of a freed pointer.

Common mistakes

Mistake 1 — Using the 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).

Mistake 2 — Assuming 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.

Mistake 3 — Forgetting to check the return value

/* 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;

Mistake 4 — Zero-filling twice

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

Debugging tips

Compiler errors

  • "too few arguments to function 'calloc'" — you used the one-argument malloc shape. Split into calloc(count, size).
  • "implicit declaration of function 'calloc'" — you forgot #include <stdlib.h>.
  • A cast warning is not needed in C; if you see one, you probably added an unnecessary (int *) cast — harmless but unidiomatic.

Runtime errors

  • Segmentation fault right after allocating — you likely did not check for NULL, or you indexed past the end of the array (counts[10] when only counts[0..9] exist).
  • Crash on 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

  • Counts or sums are off by exactly the amount of garbage — you used malloc instead of calloc and forgot to zero. Switch to calloc and re-run.

Debugging steps

  1. Compile with warnings on: gcc -Wall -Wextra -g program.c.
  2. Run under a memory checker: valgrind ./a.out reports leaks, invalid reads/writes, and double frees with line numbers.
  3. Ask yourself: did I check the return value? Are all my indices within 0 .. count-1? Is there exactly one free per successful allocation?

Memory safety

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:

  • Bounds: 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.
  • Overflow protection is the headline feature. Prefer 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.
  • Lifetime: the block lives until you 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.
  • Leaks: every successful calloc must be freed on every path out of the function, including error paths. A path that returns early without freeing leaks the block.
  • What zero means: rely on 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.

Real-world uses

Concrete uses

  • Hash tables and lookup arrays: the bucket array is calloc'd so every slot starts empty (NULL/0) without a manual loop.
  • Image and audio buffers: a freshly calloc'd frame buffer is solid black / silence, a convenient and well-defined starting state.
  • Operating systems and language runtimes: the C runtime that backs Python lists, Lua tables, and similar structures uses zeroed allocations so newly grown slots read as a defined empty value.
  • Parsers handling untrusted input: when allocating a buffer whose element count comes from a file or network header, calloc(count, size) is preferred specifically for its overflow check.

Professional best-practice habits

Beginner rules:

  • Use calloc when memory should start empty; use malloc when you will fill it immediately.
  • Always check the result against NULL before using it.
  • Pair every calloc with exactly one free; set the pointer to NULL afterwards.
  • Always size elements with sizeof(type), never a hard-coded number.

Advanced rules:

  • Prefer calloc(count, size) to malloc(count * size) for any size derived from input, to get free overflow detection.
  • Run code under valgrind or compile with AddressSanitizer (-fsanitize=address) during development to catch leaks, overflows, and use-after-free early.
  • Free on all exit paths; consider a single cleanup label (goto cleanup;) in functions with multiple error returns to keep cleanup in one place.

Practice tasks

Beginner 1 — Zeroed array, printed

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.

Beginner 2 — calloc vs malloc, observed

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.

Intermediate 1 — Digit histogram from user input

Read a line of text with fgets, then use a calloc'd array of 10 counters to count how many times each digit 09 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.

Intermediate 2 — Safe sized allocation

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.

Challenge — Growable zeroed buffer

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

Summary

  • calloc(n, sz) reserves n * sz bytes and sets every byte to 0, in one call — ideal when memory should start empty.
  • Its two-argument signature (count, element_size) lets it detect multiplication overflow and return NULL instead of a too-small buffer; malloc(n * sz) cannot.
  • All-bits-zero reliably means the value 0 for integers and byte data; for double/float and pointers, assign 0.0/NULL explicitly for strict portability.
  • Use calloc for counters, flags, and clean structs; use malloc when you will overwrite every byte immediately (the zero-fill would be wasted).
  • Always check the result for NULL, stay within indices 0 .. n-1, and free exactly once (then set the pointer to NULL).
  • The most common mistakes: using the wrong argument shape, assuming malloc zeroes, skipping the NULL check, and forgetting to free on error paths.

Practice with these exercises