FForge
← Labsdebug-08Runtime debugging●●○○

Memory leak

debug/08_memory_leak.c3 steps
0/3 steps
evidence to leave with
valgrind definitely-lost record with allocation stack; LSan report
walk it, one step at a time
run this
$ clang -g -O0 08_memory_leak.c -o bin/08_memory_leak
$ ./bin/08_memory_leak ; echo "exit=$?"
what it printed on the machine that wrote this lab
used p[0]=0x55; now leaking 100 bytes
exit=0
^ Looks perfectly healthy. That's why leaks survive code review.

There is no crash, so `gdb run` just exits 0 — gdb shows you nothing. gdb is for FAULTS (signals, wrong values), not for "memory that was never given back". For leaks, go straight to valgrind --leak-check or LSan.

run this
$ valgrind -q --leak-check=full --show-leak-kinds=all ./bin/08_memory_leak
what it printed on the machine that wrote this lab
  ==61827== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
  ==61827==    at 0x4840B26: malloc (vg_replace_malloc.c:447)
  ==61827==    by 0x400488: main (08_memory_leak.c:77)
  How to read it:
    "100 bytes in 1 blocks"     -> size and count of the leaked allocation.
    "definitely lost"           -> no pointer to it survives => a true leak.
    "at malloc ... by main (...:77)" -> the ALLOCATION call stack: the
       leak is the block born at line 77. valgrind points at where it was
       allocated (you decide where it SHOULD have been freed).
  --leak-check=full -> per-leak stack traces (not just a summary count).
  --show-leak-kinds=all -> also list "indirectly lost", "possibly lost",
       and "still reachable" blocks, not only "definitely lost".
  THE FOUR LEAK KINDS:
    definitely lost  -> no pointer at all => fix it.
    indirectly lost  -> only reachable via an already-lost block.
    possibly lost    -> only an interior pointer remains (suspicious).
    still reachable   -> a pointer exists at exit (often "intentional",
                         e.g. a global cache) — usually benign.

  ASan/LeakSanitizer finds it too, automatically at process exit:
run this
$ clang -g -O0 -fsanitize=address 08_memory_leak.c -o bin/08_memory_leak.asan
$ ./bin/08_memory_leak.asan
what it printed on the machine that wrote this lab
  ==61836==ERROR: LeakSanitizer: detected memory leaks
  Direct leak of 100 byte(s) in 1 object(s) allocated from:
      #0 0x... in malloc
      #1 0x... in main .../08_memory_leak.c:77:15
  SUMMARY: AddressSanitizer: 100 byte(s) leaked in 1 allocation(s).

ROOT CAUSE vs CRASH SITE:
  No crash site at all. The "root cause" is a missing free on the path that
  loses the last pointer. The tool reports the ALLOCATION site (line 77)
  because that's the block's identity; the FIX lives wherever ownership
  ends.
category

leak

difficulty

1 ------------------------------------------------------------

what's wrong

`malloc(100)` allocates a block that is never `free`d. When `p` goes out of scope at return, the only pointer to the block is lost — the memory stays allocated until the process exits. Buggy line: `char *p = malloc(100);` (with no free).

symptom

NONE at runtime: exits 0, output normal. A leak is invisible to a single run; it only hurts long-running programs (memory grows without bound). You need a tool to even see it.

the fix

Free what you allocate, on every path: free(p); (For CI, see --error-exitcode in the README so a leak fails the build.)

take it further

Add `free(p);` before return — both valgrind and LSan go silent ("All heap blocks were freed -- no leaks are possible"). Then `free(p); free(p);` turns it into bug #07 (double free). ============================================================

read the source · debug/08_memory_leak.c
#include <stdlib.h> #include <stdio.h> int main(void) { char *p = malloc(100); /* <-- THE BUG: never freed */ p[0] = 0x55; printf("used p[0]=0x%02x; now leaking 100 bytes\n", (unsigned char)p[0]); return 0; /* p lost here: 100 bytes leak */ }
◇
Finish the lab: write the evidence note

A lab is done when you can say what it showed on your machine — not when you have read it.