Question bank — Valgrind — detecting memory errors
Before you start, keep three words in mind (from the parent's mnemonic): Addressability ("am I allowed to touch this byte?"), Validity ("is the value here real or garbage from malloc?"), and Leak ("did I lose the only pointer to a block?"). Almost every trap here is really asking which of these three is being violated — or whether any is.
The whole page is really one picture. Before you touch a single trap, burn this map into your head — a heap block, its red zones, and the two shadow bits that live under every byte:

Look at the pale-yellow guard bytes flanking the real block: they always have their A-bit = 0. That single fact powers half the "Spot the error" answers below — stepping one byte past the block lands on a red zone, and the guard slams the door.
The A/V/L map — read this before answering
Every Valgrind message you will ever meet sorts into exactly three bins. This is the decision tree the parent's mnemonic compresses into three letters — follow an arrow from any symptom to its category:
Now watch the bits actually change. This strip shows one tiny program — malloc, read-before-write, write, free, use-after-free — and how the A-bit row and V-bit row flip at each step. Every trap in the "Spot the error" and "Edge cases" sections is just one frame of this film:

Trace it left to right: after malloc the block is touchable (A=1) but garbage (V=0); the write turns V=1; after free the whole block goes A=0 and drops into quarantine — which is exactly why the final read is caught.
And here is what the guard's report actually looks like on your terminal, colour-coded to the same three bins so you can eyeball which bit failed at a glance:

True or false — justify
A clean Valgrind run guarantees your program is bug-free.
Reading one byte before the start of a heap block is fine because it's still "your" allocation.
[a, a+size) are yours.Memcheck flags every use of an uninitialised value the instant it is read.
still reachable bytes in the leak summary are always real leaks you must fix.
still reachable means a pointer to the block still exists at exit (e.g. a global cache the OS reclaims anyway). It's often harmless; prioritise definitely lost.Compiling with -O2 gives Valgrind a more accurate picture because it's the release build.
-O0 -g; check the release build separately if needed.A program that returns exit code 0 and prints correct output has no memory errors.
Valgrind needs your source code to be recompiled with special Valgrind instrumentation.
-g so the report can name source lines — that's optional, not required.A double free() of the same pointer is caught by the V-bit machinery.
If malloc returns NULL and you write through it, Valgrind will always report an invalid write.
malloc's return, which Valgrind can't do for you.Spot the error
char *p = malloc(10); strcpy(p, "hello"); free(p); printf("%c", p[0]); — what does Valgrind say and which bit is at fault?
free, the block sits in quarantine with A-bits = 0 (last frame of figure 2), so touching p[0] is an addressability error even though the bytes still look like "hello".int *a = malloc(4*sizeof(int)); for(int i=0;i<=4;i++) a[i]=i; — where is the off-by-one and what's the message?
<= writes a[4], one element past the 4-element block — straight into the red zone of figure 1: "Invalid write of size 4, 0 bytes after a block of size 16". The A-bit at byte 16 is 0.int x; if (x > 0) foo(); — is this a Valgrind error, and what would it report?
x is an uninitialised stack variable, so branching on it gives "Conditional jump or move depends on uninitialised value(s)" — a V-bit error. (Note: Memcheck tracks stack values too, not only heap.)char *buf = malloc(100); buf = malloc(200); free(buf); — which block leaks and how is it classified?
buf no pointer holds its address, so the exit scan finds it unreachable ⇒ "definitely lost". The 200-byte block was freed and is fine.free(p); free(p); where p isn't nulled between — safe or not?
p = NULL after the first free makes the second free(NULL) a no-op.int *p = malloc(sizeof(int)); int y = *p + 1; if (y == 5) ... — where does Valgrind finally complain?
if, not at *p + 1. The undefined V-bits ride through the addition into y, and only the conditional jump — where garbage changes control flow — triggers the report. --track-origins=yes would point back to the malloc.Why questions
Why does Valgrind quarantine freed blocks on a freelist instead of returning them to the allocator immediately?
Why does Memcheck keep two separate bit-tracks (A and V) instead of one "is this OK" flag?
Why does --track-origins=yes cost noticeable speed?
Why does Valgrind slow a program down by roughly 10–50×?
Why is -g recommended even though Valgrind runs without it?
-g embeds debug info so the report maps hex addresses back to source file, line, and variable names — turning "Invalid write at 0x4005f2" into "at main (prog.c:4)". It changes readability, not detection.Why can Memcheck catch heap overflows but not many stack-array overflows?
Edge cases
free(NULL); — error or silently fine?
free(NULL) as a no-op, and Valgrind treats it as such — no report.malloc(0) returns a pointer — is writing one byte to it OK?
At program exit you free everything except one still-in-scope global pointer's block — leak or not?
still reachable, not definitely lost: the global pointer still points into it, so the mark-and-sweep scan finds it reachable. Not a "lost" leak, though it is technically unfreed.A block is only pointed to by a pointer to its middle, not its start — how is it classified?
possibly lost: only an interior pointer reaches it. Valgrind can't be sure that address is meant as a live reference, so it hedges rather than calling it definitely lost.You leak block B, but B was the only pointer to block C — how is C classified?
indirectly lost: C is unreachable only because the lost block B held its pointer. The root leak is B; fixing B's leak would resolve C too.An uninitialised value is written to a file via a syscall — silent or reported?
write) is an observable use of garbage, so Memcheck flags "syscall param ... points to uninitialised byte(s)" — same principle as the conditional-jump case.Reading uninitialised memory that you never branch on and never output — bug or not, per Memcheck?
Recall One-line self-check
If you can state, for any Valgrind message, whether it's an A, a V, or an L problem (walk the flowchart above) — and name the boundary case that produced it — you've internalised this bank. Anything that fits none of those three isn't a Memcheck bug; reach for GDB instead.