Dataslope logoDataslope

The Heap and Dynamic Allocation

malloc, calloc, realloc, free — how to ask the runtime for memory and give it back

The stack is fast but rigid: every allocation dies when its function returns. To make memory outlive the function that created it — to build a linked list, a dynamic array, a parsed AST, anything — you ask the runtime for a piece of the heap with malloc and return it later with free.

Manual heap management is C's most distinctive (and most dangerous) feature. This page is the long, careful introduction.

The four heap functions

FunctionPurposeReturns
malloc(n)Reserve n bytes, contents uninitializedvoid * or NULL on failure
calloc(count, size)Reserve count * size bytes, zeroedvoid * or NULL
realloc(p, n)Resize an existing block; may move itvoid * (new ptr) or NULL
free(p)Release a block previously returned by malloc/calloc/reallocvoid

They all live in <stdlib.h>.

Your first malloc

Code Block
C 17 (201710L)

Three rules visible in that tiny example:

  1. Always check for NULL. malloc can fail.
  2. Initialize before reading. malloc does not zero memory; reading uninitialized bytes is undefined behavior.
  3. Free exactly once. Not zero times (leak). Not two times (double-free, often crash). Exactly once, then stop using the pointer.

A growable array of N ints

This is the most common use of malloc: you only know the size at runtime.

Code Block
C 17 (201710L)

The `sizeof *ptr` idiom

Writing malloc(n * sizeof *arr) instead of malloc(n * sizeof(int)) means the line stays correct if you ever change arr's element type. It is a small habit that prevents large bugs.

calloc: zeroed and overflow-checked

calloc(count, size) does two extra things compared to malloc(count * size):

  1. It zeroes the memory before returning.
  2. It checks count * size for integer overflow.
Code Block
C 17 (201710L)

Prefer calloc whenever you would otherwise call malloc followed by memset(p, 0, ...).

realloc: grow or shrink

realloc(p, new_size) is the resizing primitive. It may:

  • expand the existing block in place (best case), or
  • allocate a new block, copy the old contents, and free the old one,
  • or return NULL (the old block is still valid in this case).

Never assign realloc's result back over the only pointer

p = realloc(p, n); leaks the original block if realloc fails. The safe pattern is to use a temporary.

Code Block
C 17 (201710L)

Building a dynamic array (mini std::vector)

A small, complete vec type to show how growable arrays really work.

Code Block
C 17 (201710L)

Doubling the capacity each time gives amortized O(1) push. This is exactly how std::vector<T> in C++ and Vec<T> in Rust grow.

Ownership: who frees what?

C has no automatic memory management. Someone must call free on every heap block exactly once. The way to keep this manageable is to make ownership explicit:

  • The function that creates a block usually documents whether it returns the block to the caller (caller frees) or keeps it internally (the library frees later).
  • The function that takes a block usually documents whether it takes ownership (and will free it) or just borrows it (caller still frees).
  • A struct with a heap-allocated member usually has a matching _free function (or _destroy, or _release) that knows how to clean up.
Code Block
C 17 (201710L)

Allocating into a caller's pointer

Sometimes the function needs to allocate and replace the caller's pointer with the new address. That's a pointer-to-pointer parameter.

Code Block
C 17 (201710L)

Memory leaks

A leak is a heap block that the program no longer has any pointer to but has not freed. The block sits there until the process exits. Long-running programs (servers, daemons, GUIs) that leak slowly may eventually exhaust memory and crash.

Code Block
C 17 (201710L)

We will see leaks again — alongside double-frees and use-after-frees — in the Memory Bugs chapter.

Practice: average of N numbers from the heap

Challenge
C 17 (201710L)
Allocate, fill, average, free

Allocate a heap array of N = 5 ints, fill it with the values 10, 20, 30, 40, 50 (in that order), print their integer average, then free the array. The output must be exactly 30.

Practice: heap-allocated string copy

Challenge
C 17 (201710L)
Implement my_strdup

Implement char *my_strdup(const char *s) that returns a heap-allocated copy of s (caller frees it). The provided main calls it, prints the copy, and frees it.

Test your understanding

QuestionSelect one

What is the safest way to handle a failed realloc?

Assume it succeeded and keep using the same pointer.

Assign the result to a temporary first; if NULL, free the old block (which is still valid) and bail out.

Call realloc again immediately; the second call usually succeeds.

Use free on the original block and then malloc a new one.

QuestionSelect one

Why does malloc not zero-initialize the memory it returns?

The standard requires that it does.

Zeroing is impossible without kernel support.

Zeroing has a cost; programs that are about to overwrite every byte anyway should not pay it. calloc exists for when you actually want zeros.

Because malloc always returns memory that was previously freed, which is already zeroed.

QuestionSelect one

After free(p);, what should you do with p?

Read it once more to confirm the contents were cleared.

Pass it to free again to be safe.

Print it to stdout for debugging.

Stop using it (and ideally set it to NULL so accidental later uses become immediate NULL-pointer crashes instead of silent corruption).

On this page