5.1.14 · D3C Programming

Worked examples — Dynamic memory — malloc, calloc, realloc, free

3,795 words17 min readBack to topic

This page is the drill-ground for the parent topic. We march through every kind of case a heap-allocation problem can throw at you: normal sizes, the number zero, degenerate (empty) requests, allocation failure, growing, shrinking, integer overflow of the byte count, a word problem, and an exam trap. Nothing is left unshown.

You should already have met Pointers in C, the sizeof operator, and The Stack and the Heap. If a symbol here feels unfamiliar, that's the cue to revisit those.


The scenario matrix

Think of every heap call as a point on a grid. The two axes that matter are "how much am I asking for?" (the size you request) and "what is the allocator's answer?" (a real block, or NULL). Here is the full grid — every cell gets a worked example below.

Cell Situation What's tricky about it Covered by
C1 Normal malloc of n objects, success deriving byte count, garbage init Example 1
C2 calloc for zeroed data why zero matters, two-arg form, its own multiply overflow Example 2
C3 malloc(0) — the zero-size request may return NULL or a freeable non-NULL Example 3
C4 Allocation failure (NULL returned) + byte-count overflow must check; product can wrap Example 4
C5 realloc to grow, block moves old pointer invalid after, copy happens Example 5
C6 realloc to shrink data past new size is lost Example 6
C7 realloc(NULL, n), realloc(p, 0), realloc(NULL, 0)degenerate edges behave like malloc/free/malloc(0) Example 7
C8 Word problem: read unknown-length input combine grow-loop + free Example 8
C9 Exam twist: spot the bug self-assign leak + overflow Example 9

The sign/quadrant idea from geometry maps here onto sizes: positive-normal (C1, C2), zero (C3, C7), and the "answer is nothing" case (C4). Growing vs shrinking (C5, C6) are the two directions of change. A hidden fourth axis is whether the product n * sizeof(*p) itself overflows before malloc ever sees it (C4, C9): even a "small-looking" request can wrap to a tiny number and hand you a block that is dangerously undersized.

The picture below is a map of that whole grid, so read it carefully:

  • The horizontal line is the size axis — how many bytes you ask for grows as you move right. The left end is size 0, the middle is a normal request, the right end is "too big to satisfy."
  • The three coloured dots are the three answer-classes. The pink dot (far left) is the zero-size request (C3, C7) — the allocator may answer NULL or a freeable non-NULL. The blue dot (middle) is a normal success (C1 malloc, C2 calloc). The yellow dot (right) is a request so large the allocator gives up and returns NULL (C4).
  • The two arrows above the line are realloc: the blue arrow points right (grow, C5) and the pink arrow points left (shrink, C6)realloc is literally movement along this size axis.
  • The small fold-under lane beneath the axis is the hidden fourth axis: it shows a large request n × size wrapping around back near zero. That is why a request that "should" land on the yellow dot can secretly land near the pink dot with a block far too small — the danger C4 and C9 warn about.
Figure — Dynamic memory — malloc, calloc, realloc, free

Figure — Dynamic memory — malloc, calloc, realloc, free

Active Recall

Recall (C3) What are the two legal outcomes of

malloc(0)? Either NULL, or a unique non-NULL pointer you must not dereference but may free. Always free the result.

Recall (C2/C4) What is

SIZE_MAX, and how do you guard count * size against overflow? SIZE_MAX is the largest value a size_t can hold. Guard with if (count > SIZE_MAX / size) ... before multiplying — the wrap cannot be detected after it happens. calloc's own overflow check is only implementation-defined, not guaranteed.

Recall (C4) What is the value of a pointer after

malloc fails, and why compute sizes in size_t? After failure the pointer is exactly NULL (integer 0) — always check first. Use size_t (not long) so n * sizeof(*p) is computed in the same width malloc expects and cannot silently wrap on 32-bit long platforms.

Recall (C5 vs C6) After

realloc, how many bytes of old data are guaranteed intact? min(old_size, new_size) bytes. Growing keeps everything; shrinking keeps only the leading kept region.

Recall (C7) What do

realloc(NULL, n) and realloc(NULL, 0) do? realloc(NULL, n) behaves like malloc(n) — a fresh allocation. realloc(NULL, 0) reduces to malloc(0) — may return NULL or a freeable non-NULL pointer.

Recall (C8) Starting at capacity 2 and doubling, how many doublings to fit 5 items?

Two: 2 → 4 → 8.