Manual Management Pitfalls
20 min
Languages without a garbage collector (C, C++, and similar) require the
programmer to explicitly free every allocation — and getting this
wrong produces two of the most notorious bug classes in software:
double-free (freeing the same pointer twice, corrupting the
allocator's internal bookkeeping) and use-after-free (reading or
writing through a pointer after its memory has already been freed and
potentially reused for something else entirely — a serious security
vulnerability class, not just a correctness bug).
def detect_memory_bugs(operations):
allocated = set()
bugs = []
for kind, ptr in operations:
if kind == "alloc":
allocated.add(ptr)
elif kind == "free":
if ptr not in allocated:
bugs.append(("double-free", ptr))
else:
allocated.remove(ptr)
elif kind == "use":
if ptr not in allocated:
bugs.append(("use-after-free", ptr))
return bugs
This exact class of bug is precisely why Rust's ownership/borrow checker exists -- it rejects code with a possible use-after-free or double-free AT COMPILE TIME, and why garbage-collected languages (Python, Java, Go) sidestep the whole category by never letting the programmer free anything manually at all.
Write `detect_memory_bugs(operations)`: `operations` is a list of `('alloc', ptr)`, `('free', ptr)`, or `('use', ptr)`. Track which pointers are currently allocated. Record `('double-free', ptr)` if `'free'` is called on a pointer that isn't currently allocated, and `('use-after-free', ptr)` if `'use'` is called on one. Return the list of bugs found, in order.
In a language with manual memory management (C, C++), why is USE-AFTER-FREE considered one of the most dangerous classes of bug, security-wise?
You can detect double-free and use-after-free bugs by tracking allocation state, and understand why use-after-free is a serious security concern, not just a crash.