Foundations — Valgrind — detecting memory errors
Before you can read the parent note Valgrind — detecting memory errors, you need to earn every word it throws at you. Below, each idea is built from nothing: plain words → a picture → why the topic needs it. Read top to bottom; each rung stands on the one below.
1. A byte and an address — the numbered row of boxes
Picture a long row of lockers. Each locker is a byte. The little number painted on each locker door is its address. When C "accesses memory," it walks up to a locker (address), and either reads what's inside or writes a new value into it.

2. A pointer — a box that stores a box-number
The notation:
int *p— read as "pis a pointer to anint." The*in the declaration means "this thing points at something."*p— read as "the value at the lockerppoints to." Here*means "go there and look inside." This is called dereferencing.p(alone) — the locker number itself.

Why the topic needs it: the parent note writes *p == 0 and a[5] = 42. Both are pointer operations. a[5] is secretly *(a + 5) — "go 5 slots past where a points, then look inside." More in Pointers in C.
3. Stack vs Heap — two neighbourhoods of memory
Picture two neighbourhoods:
- Stack — a stack of trays. Each function call adds a tray on top; when it returns, its tray is thrown away automatically. You never call
freehere. - Heap — a warehouse where you ask for storage space ("give me 20 bytes") and you are responsible for handing it back.
Valgrind's whole job is watching the heap, because the heap is where you hold the responsibility — and therefore where you make mistakes. See Stack vs Heap.
4. malloc and free — renting and returning heap boxes
Two crucial facts the parent leans on heavily:
- A fresh
mallocblock contains garbage — leftover junk from whoever used those lockers before. C does not clear it. - After
free(p), the pointerpstill holds the old address, but you are no longer allowed to use it. Touching it = use-after-free.
More detail: malloc and free.
5. sizeof and array indexing — turning counts into byte offsets
sizeof(int) is usually 4 (four bytes per integer). So in the parent's overflow example, malloc(5 * sizeof(int)) rents bytes: lockers through (relative to the block's start).
Then a[5] means byte — the first locker past the block. That is exactly why Valgrind says "0 bytes after a block of size 20": your write starts precisely where the rented block ends.

Recall Quick check: which byte does
a[3] write for an int array?
Bytes 12, 13, 14, 15 — that is, up to . Inside a 20-byte block, this is safe.
6. The two shadow labels — A-bits and V-bits
Now the payoff. Valgrind keeps, for every real byte, two hidden tags:
Two different questions:
| Situation | A-bit | V-bit | Valgrind error |
|---|---|---|---|
| Read a freed box | 0 (not yours) | — | Invalid read (use-after-free) |
Branch on fresh malloc value |
1 (yours) | undefined | "Conditional jump depends on uninitialised value" |
| Write one past the array | 0 (past the block) | — | Invalid write |
The parent formalises the A-bit rule as: an access of n bytes at address is OK only if every byte has its A-bit set to accessible. If even one is 0, it's an error.
7. Undefined Behaviour — why "it worked" proves nothing
The picture: UB is a coin that lands heads (works) most days purely by luck of the heap layout. Change the compiler, the input, or the moon phase, and it lands tails (crash / corruption). Valgrind exists precisely to catch UB before it flips to tails on a customer. See Undefined Behaviour in C.
Prerequisite map
Each foundation on the left feeds the shadow-memory idea on the right. Valgrind is only meaningful once addresses, pointers, the heap, and UB all sit in place. Related tooling worth a glance later: AddressSanitizer (ASan) and Debugging with GDB.
Equipment checklist
An address is what, physically?
What does int *p declare vs what does *p do?
int *p declares p as a pointer to an int; *p dereferences it — goes to the box p points at and reads/writes its contents.Which memory region does Valgrind mainly watch, and why?
malloc/free, so that's where your mistakes live.What is inside a fresh malloc block?
malloc does not clear it.How many bytes does malloc(5 * sizeof(int)) rent on a typical machine?
sizeof(int) is 4 and 5 × 4 = 20.