Valgrind — detecting memory errors
WHY does this tool exist?
C gives you raw pointers and no automatic memory management. The CPU happily reads/writes any address; if you malloc(10) and write byte 11, nothing crashes immediately — you corrupt the heap and the program dies "randomly" 5000 lines later. These bugs are:
- Non-deterministic (depend on heap layout, timing).
- Silent (no compiler warning, often no crash).
WHAT it tracks (the core trick — shadow memory): For every byte of real memory, Memcheck keeps two bits of info:
- A-bits (Addressability): is this byte allowed to be accessed right now?
- V-bits (Validity/defined-ness): does this byte hold a defined value, or garbage from
malloc?
HOW Memcheck reasons (derivation from first principles)
We don't have a formula here — we have a decision procedure. Let's build it.
Let address and access size . For a read or write of bytes starting at :
If any byte is not accessible ⇒ Invalid read/write error.
For defined-ness, V-bits propagate through operations. Starting rules:
The leak rule (mark-and-sweep at exit): at program end, Memcheck does a GC-style scan. A heap block is:
- Still reachable: a pointer to it exists somewhere → not a "lost" leak (but still unfreed).
- Definitely lost: no pointer anywhere points into it → you leaked it.
- Indirectly lost: only reachable through another lost block.
- Possibly lost: only an interior pointer points to it.

Worked Example 1 — Heap buffer overflow
#include <stdlib.h>
int main(void) {
int *a = malloc(5 * sizeof(int)); // 20 bytes, A-bits=1, V-bits=undef
a[5] = 42; // writes bytes 20..23 -> A-bits=0!
free(a);
return 0;
}Run: gcc -g -O0 prog.c && valgrind --leak-check=full ./a.out
Valgrind says:
Invalid write of size 4
at main (prog.c:4)
Address 0x... is 0 bytes after a block of size 20 alloc'd
Worked Example 2 — Use of uninitialised value
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int)); // V-bits = undefined
if (*p == 0) // branching on undefined value!
printf("zero\n");
free(p);
return 0;
}Conditional jump or move depends on uninitialised value(s)
Worked Example 3 — Memory leak
#include <stdlib.h>
int main(void) {
char *buf = malloc(100);
buf = malloc(200); // first 100-byte block now unreachable
free(buf); // frees the 200, leaks the 100
return 0;
}LEAK SUMMARY:
definitely lost: 100 bytes in 1 blocks
Recall Explain to a 12-year-old (hidden)
Imagine you're given a row of 5 lockers to keep your stuff. C lets you stuff things into locker number 6 — which isn't yours — and nobody stops you. Valgrind is a strict guard standing behind you. Every time you open a locker, the guard checks: "Is this one yours?" (addressability) and "Did you actually put something in here, or is it empty junk?" (defined-ness). At the end of the day the guard also checks: "Did you leave any of your bags lying around without telling anyone where they are?" — those are leaks. The program runs slower because the guard checks everything, but you finally catch all your sneaky mistakes.
Flashcards
What virtual machine technique lets Valgrind see every memory access?
What are A-bits and V-bits in Memcheck?
A read of freed memory is which kind of error?
Branching on a malloc'd-but-unwritten variable triggers what?
Why does Valgrind keep freed blocks in a quarantine freelist?
Difference between "definitely lost" and "still reachable"?
Why compile with -g -O0 before running Valgrind?
What does --track-origins=yes add?
Why can a program "run fine" yet have a Valgrind error?
What flag actually shows full leak details?
Connections
- malloc and free — the allocations Valgrind shadows
- Pointers in C — invalid/dangling pointers are what A-bits guard
- Undefined Behaviour in C — why these bugs are silent
- Stack vs Heap — Memcheck mainly polices the heap
- AddressSanitizer (ASan) — compiler-based alternative, faster, different trade-offs
- Debugging with GDB — pair with Valgrind for root-cause analysis
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho, C mein aap pointers se kuch bhi memory address access kar sakte ho, aur CPU bina sawaal kiye allow kar deta hai. Agar aapne malloc(20 bytes) kiya aur galti se byte 21 par likh diya, toh program abhi crash nahi karega — par heap corrupt ho jayega aur 5000 line baad "randomly" crash karega. Yahi C ke sabse tricky bugs hain: silent aur non-deterministic. Valgrind in bugs ka guard hai.
Valgrind aapke program ko ek virtual CPU par chalata hai aur har memory access ko watch karta hai. Iska main trick hai shadow memory — har byte ke liye do cheezein yaad rakhta hai: A-bits (kya yeh byte access karne ki permission hai?) aur V-bits (jo value padi hai woh defined hai ya malloc ka garbage?). Freed memory padhna A-bit error hai (yeh ab tumhari nahi). Uninitialised variable par if lagana V-bit error hai (touch kar sakte ho, par value kachra hai).
Practically: hamesha gcc -g -O0 se compile karo (taaki Valgrind exact line aur variable naam bata sake), phir valgrind --leak-check=full --track-origins=yes ./a.out chalao. Report mein "Invalid write/read", "uninitialised value", aur leak summary dikhega. Yaad rakhna: "program toh sahi chal raha tha" iska matlab bug nahi hai — woh undefined behaviour hai jo aaj heap luck se chal gaya. Aur leaks mein sabse important hai "definitely lost" — usko pehle fix karo, "still reachable" aksar harmless hota hai. Mnemonic: A-V-L — Address allowed? Value real? Left behind kuch?