Exercises — Common memory errors — null dereference, buffer overflow, use-after-free, double free, memory leak
Before we start, one picture of the vocabulary every problem uses. A pointer is a sticky note carrying a locker number; the heap is the row of lockers malloc hands out; free returns a locker to the desk without erasing your sticky note.

Prerequisites you may want open: Pointers in C, malloc and free, Stack vs Heap Memory, Undefined Behaviour, Strings in C and the null terminator, Valgrind and AddressSanitizer.
L1 — Recognition
Exercise 1.1
Name the memory error in each snippet (choose from: null dereference, buffer overflow, use-after-free, double free, memory leak).
// (a)
int *p = malloc(sizeof(int));
free(p);
free(p);
// (b)
char *q = malloc(50);
q = malloc(80);
free(q);
// (c)
int *r = NULL;
*r = 5;
// (d)
char buf[4];
strcpy(buf, "data"); // "data" = 4 chars + '\0'Recall Solution 1.1
(a) Double free — free(p) is called twice on the same address; the second call corrupts the allocator's free-list.
(b) Memory leak — q is overwritten by the second malloc, so the first 50-byte block loses its only pointer. free(q) only frees the 80-byte block. Leaked bytes: .
(c) Null dereference — r holds NULL (address 0), writing *r traps.
(d) Buffer overflow — "data" needs bytes but buf holds only . It writes byte past the end (the \0).
L2 — Application
Exercise 2.1
Fix this so it has no memory error. State each fix and why.
int *p = malloc(sizeof(int) * 3);
p[3] = 9;
printf("%d\n", *p);
free(p);
free(p);Recall Solution 2.1
Three bugs. The corrected version:
int *p = malloc(sizeof(int) * 3);
if (p == NULL) return 1; // fix 1: check malloc → no null deref
p[2] = 9; // fix 2: valid indices 0..2, not p[3]
printf("%d\n", *p);
free(p);
p = NULL; // fix 3: null-after-free
free(p); // now free(NULL) → guaranteed no-op- Fix 1 guards against
mallocreturningNULL. - Fix 2: an array of has valid indices .
p[3]is one past the end (overflow). - Fix 3: setting
p = NULLmakes the secondfree(p)a safe no-op, killing the double free.
Exercise 2.2
Rewrite safely using a size-bounded copy. src may be any length.
char dst[16];
char *src = get_user_input(); // could be 100 chars
strcpy(dst, src);Recall Solution 2.2
char dst[16];
char *src = get_user_input();
snprintf(dst, sizeof dst, "%s", src); // copies at most 15 chars + '\0'snprintf writes at most sizeof dst - 1 = 15 characters and always adds the \0, so it can never exceed . The invariant is now enforced by the function itself.
L3 — Analysis
Exercise 3.1
Trace this program. For each numbered line say what happens to the memory and whether it is legal.
int *a = malloc(sizeof(int)); // (1)
*a = 100; // (2)
int *b = a; // (3)
free(a); // (4)
printf("%d\n", *b); // (5)Recall Solution 3.1
- (1) Allocates a 4-byte block on the heap;
aholds its address. Legal. - (2) Writes
100into that block. Legal. - (3)
bis a copy of the same address — two sticky notes, one locker. Legal (aliasing). - (4) Returns the block to the allocator.
ais now dangling; but so isb, because it names the same freed address. - (5) Use-after-free through
b. Settinga = NULLafter (4) would not save you here —bstill holds the old address. This is why "one owner per block" matters.
Key insight: nulling the pointer you freed does not null its aliases.
Exercise 3.2
Given this program, compute the number of allocations , the number of frees of distinct live blocks , and the leaked bytes.
char *x = malloc(30);
char *y = malloc(40);
x = malloc(50); // reassigns x
free(y);
free(x);Recall Solution 3.2
Blocks created: -byte, -byte, -byte .
x = malloc(50)overwrote the pointer to the -byte block → that block is now unreachable.free(y)frees the -byte block.free(x)frees the -byte block. So distinct live blocks freed.- Since (), there is a leak of the -byte block. Leaked bytes .
L4 — Synthesis
Exercise 4.1
This function is meant to duplicate a string onto the heap. It has two distinct bugs. Find both and write the fully correct version.
char *mydup(const char *s) {
char *out = malloc(strlen(s)); // (A)
strcpy(out, s); // (B)
return out;
}Recall Solution 4.1
Bug 1 (line A): allocates strlen(s) bytes but a copy needs strlen(s) + 1 for the \0. Line B then overflows by 1 byte.
Bug 2 (lines A–B): malloc is never checked; if it returns NULL, strcpy is a null dereference.
Corrected:
char *mydup(const char *s) {
char *out = malloc(strlen(s) + 1); // +1 for '\0'
if (out == NULL) return NULL; // check malloc
strcpy(out, s); // now fits exactly
return out;
}For s = "hello": strlen = 5, correct allocation bytes.
Exercise 4.2
Apply the parent's mnemonic CONF-B (Check, Owner, Null-after-free, Free-once, Bounds). For each of the five errors below, state which single CONF-B rule would have prevented it.
*pwheremallocreturnedNULL.- Writing
a[5]inint a[5]. - Reading
*pafterfree(p). free(p); free(p);.p = malloc(...)overwriting a live pointer.
Recall Solution 4.2
- Null deref → Check malloc.
- Overflow → Bounds (indices ).
- Use-after-free → Null-after-free (a later
*pbecomes a detectable null deref). - Double free → Free once (plus Null-after-free makes the 2nd
free(NULL)a no-op). - Leak → One owner per block (don't overwrite the only handle).
Rule count: 4 of the 5 bugs are killed by Null-after-free + Owner + Check + Bounds — exactly the parent's "one discipline kills four of five" claim.
L5 — Mastery
Exercise 5.1
A long-running server calls handle() once per request. Each call leaks a fixed number of bytes. Given the leak below, how many bytes leak per 1000 requests, and after how many requests does the total leak reach bytes (round up)?
void handle(void) {
char *log = malloc(64);
char *buf = malloc(256);
// ... uses both ...
free(buf);
// forgot free(log);
}Recall Solution 5.1
Each call leaks the -byte log block ( buf is correctly freed). So leak per call bytes.
- Per requests: bytes.
- To reach bytes: requests.
Because it never crashes on request 1, a leak like this is invisible until the OOM-killer strikes — exactly why tools matter.
Exercise 5.2
You run this program under Valgrind and AddressSanitizer. For each line, name the exact diagnostic a tool would report (choose: "Invalid write", "Invalid read", "Invalid free / double free", "definitely lost").
char *p = malloc(8); // (1)
p[8] = 'x'; // (2)
free(p); // (3)
char c = p[0]; // (4)
free(p); // (5)
char *q = malloc(16); // (6) — never freedRecall Solution 5.2
- (2)
p[8]is index 8 in an 8-byte block (valid ) → Invalid write (heap-buffer-overflow). - (4) reading
p[0]afterfree(p)→ Invalid read (heap-use-after-free). - (5) freeing the already-freed block → Invalid free / double free.
- (6)
q's block is never freed and its pointer is lost at exit → definitely lost (leak) of bytes. - Line (3) alone is legal.
Total distinct errors flagged: . This is why AddressSanitizer/Valgrind is the mastery-level safety net — it makes every silent UB from this page loud.
Recall Quick self-check tally
Bugs you should now be able to name, fix, trace, prevent, and detect: null deref, buffer overflow, use-after-free, double free, memory leak. If any level felt shaky, reread its [!mistake] box — each is the single trap the grader expects at that difficulty.
Reveal-line drills: