Skip to content

Intermediate · Dynamic memory · topic 29 of 60 · 30 min

Memory leaks and other heap bugs

Explanation

The classic heap bugs are leaks (memory never freed), use-after-free, double free, heap buffer overflows, freeing memory not from malloc, and reading uninitialised memory. They cause slow growth, crashes and security holes. valgrind (memcheck) and AddressSanitizer (-fsanitize=address) find them, pointing at the exact lines where memory was allocated, freed and misused.

The bug catalogue

c
/* 1. Leak: the only pointer is lost */
char *buf = malloc(100);
buf = malloc(200);            /* the first 100 bytes can never be freed */

/* 2. Use after free */
int *p = malloc(sizeof *p);
free(p);
*p = 5;                       /* writes into memory that may belong to someone else now */

/* 3. Double free */
free(p);
free(p);                      /* corrupts the allocator; often an immediate crash */

/* 4. Heap buffer overflow */
int *a = malloc(4 * sizeof *a);
a[4] = 1;                     /* one past the end */

/* 5. Freeing what was not allocated */
int x;
free(&x);                     /* x is on the stack */

/* 6. Uninitialised read */
int *q = malloc(sizeof *q);
if (*q > 0) { }               /* garbage decides the branch */

valgrind

bash
gcc -g -O0 prog.c -o prog
valgrind --leak-check=full ./prog
valgrind runs on Linux (and WSL on Windows); the program runs much slower under it.
output
==12345== Invalid write of size 4
==12345==    at 0x10916D: main (prog.c:14)
==12345==  Address 0x4a8e050 is 0 bytes after a block of size 16 alloc'd
==12345==    at 0x483B7F3: malloc (vg_replace_malloc.c:309)
==12345==    by 0x109156: main (prog.c:13)
…
==12345== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x483B7F3: malloc (vg_replace_malloc.c:309)
==12345==    by 0x109146: main (prog.c:9)
Read: what went wrong (Invalid write), where (prog.c:14), and where the block came from (prog.c:13).

AddressSanitizer

output
gcc -g -fsanitize=address prog.c -o prog && ./prog
==… ==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010
WRITE of size 4 at 0x602000000010 thread T0
    #0 main prog.c:20
freed by thread T0 here:
    #1 main prog.c:19
previously allocated by thread T0 here:
    #1 main prog.c:18
ASan is much faster than valgrind (often around 2× slowdown) and works with gcc and clang on Linux and macOS; it also detects leaks on Linux by default.

Habits that prevent heap bugs

  • Pair every allocation with exactly one free, ideally in the same function or a matching destroy function.
  • Set pointers to NULL after free; free(NULL) is safe, double free of NULL cannot happen.
  • Keep sizes next to buffers and check indexes.
  • On error paths, free what was already allocated (a single clean-up section at the end of the function, sometimes with goto, is common in C).
  • Run tests under ASan or valgrind regularly, for example in CI.
Careful: A program with a leak usually works fine in short tests and fails in production after running for days (a server whose memory grows until the operating system kills it). Use-after-free and double free are worse: attackers exploit them to take control of programs. Treat every report from these tools as a real bug.
Think of it like this: Heap bugs are like mistakes in a hotel's room register: a leak is a guest who left without checking out, so the room is never given to anyone again; use-after-free is a guest who checked out but kept the key and walks into the room after a new guest moved in; double free is checking out the same room twice and confusing the register.
Tip: Make a habit: every time you write malloc, immediately write the free (and the NULL check) before writing the code in between.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Memory leaks and other heap bugs · 1/6

Heap bugs

In this lesson you will learn the classic heap bugs and the tools that find them.

Prefer a video by a teacher? Find free videos on “valgrind memcheck tutorial memory leak use after free addresssanitizer” on YouTube ↗

In real life: A leak in a long-running logger

A logging daemon on a payment gateway formatted each log line into a freshly malloc'd buffer and forgot to free it on one error path. Memory grew by a few MB per hour, and the service restarted every two days. valgrind --leak-check=full pointed to the exact malloc line; adding the missing free fixed it.

c
char *line = format_line(event);       /* allocates */
if (!line) return -1;
if (write_log(line) != 0) {
    free(line);                         /* the missing free on the error path */
    return -1;
}
free(line);
return 0;

Practice (0/3 done)

Task 1 · Question

valgrind reports "100 bytes in 1 blocks are definitely lost". What kind of bug is it?

Task 2 · Question

Which line is a use-after-free? 1: char *s = malloc(8); 2: strcpy(s, "upi"); 3: free(s); 4: s[0] = 'U';

Task 3 · Practice

Write a deliberately buggy C program containing one leak, one heap buffer overflow and one use-after-free. Run it with -fsanitize=address and with valgrind (on Linux or WSL), and copy the key line of each report. Then fix the three bugs and show clean reports.

Quick quiz

Question 1

1.What does a double free usually cause?

Question 2

2.Which tool is typically faster: valgrind or AddressSanitizer?

Question 3

3.Why compile with -g when using these tools?

Question 4

4.Which is NOT a heap bug?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does a double free usually cause?

Answer: Heap corruption, often a crash or a security hole

The allocator's bookkeeping is corrupted.

Which tool is typically faster: valgrind or AddressSanitizer?

Answer: AddressSanitizer

ASan instruments at compile time.

Why compile with -g when using these tools?

Answer: So reports show file names and line numbers

Debug info maps addresses to source lines.

Which is NOT a heap bug?

Answer: Integer division by zero

That is an arithmetic error, not a heap bug.

On which systems does valgrind run best?Related

Answer: Linux (including WSL)

Use WSL on Windows.

From Finding memory bugs with valgrind

How does Rust decide when to free heap memory?Related

Answer: Ownership rules let the compiler insert the free at the right point

Rust frees a value when its owner goes out of scope, decided at compile time.

From Memory 101: the stack and the heap

Roughly how much does ASan slow a program down?Related

Answer: About 2×

Much faster than valgrind.

From Sanitizers and warnings

What is a buffer overflow?Related

Answer: Writing past the end of an array into neighbouring memory

It corrupts other data.

From Buffer overflows and safe string handling

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Dynamic memory” before its exam: Dynamic strings and 2D arrays.
  2. Module 6 exam: Dynamic memory · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Structs, unions, enums and files. 60 topics left in the course before the final exam.

← Previous: realloc and growing arrays