14.4 Common Errors β the gallery
Every specimen below compiles without a peep. Thatβs the lesson hiding under the lesson: compiling a C program is necessary for correctness, and nowhere near sufficient. (Memory management is such a reliable source of pain that newer languages hired a garbage collector garbage collector Runtime machinery in newer languages that frees unreferenced memory for you β at the cost of keeping-a-reference leaks. defined in ch. 14 β open in glossary to free unreferenced memory automatically β though, as specimen #4 shows, even that isnβt a full pardon.)
1 β Forgetting to allocate
char *src = "hello";
char *dst; // oops! unallocated
strcpy(dst, src); // segfault and die
strcpy expects dst to point at real memory; it points at garbage.
The likely outcome is a segmentation
fault segmentation fault The OS's response to an illegal memory access β "you did something wrong with memory, foolish programmer" (name explained with segmentation).
defined in ch. 14 β open in glossary
:
Segmentation fault (n.) β a fancy term for YOU DID SOMETHING WRONG WITH MEMORY YOU FOOLISH PROGRAMMER AND I AM ANGRY. (Why βsegmentationβ? Chapter 16 explains β if that isnβt incentive to read on, what is?)
The fix: malloc(strlen(src) + 1) for dst first β or just use
strdup() and make your life easier.
2 β Not allocating enough: the buffer overflow
char *src = "hello";
char *dst = (char *) malloc(strlen(src)); // too small!
strcpy(dst, src); // "works" β¦
One byte short (the terminator). This buffer overflow buffer overflow Writing past an allocation's end (e.g., forgetting strlen+1); sometimes silently "works," sometimes crashes, often a security hole. defined in ch. 14 β open in glossary will often run seemingly correctly: maybe the overwritten byte belonged to nothing important, maybe malloc padded the allocation anyway. Sometimes it tramples a live variable; sometimes it crashes; and this exact species of bug is the source of many security vulnerabilities. The lesson, again: ran once β correct.
3 β Forgetting to initialize
malloc hands you bytes of unknown value. Read them before writing β an uninitialized read uninitialized read Reading malloc'd memory before writing it β you get whatever unknown bytes were there. defined in ch. 14 β open in glossary β and you get whatever was there: zero if youβre lucky, something random and harmful if youβre not.
4 β Forgetting to free: the memory leak
The memory leak memory leak Forgetting to free memory; slowly fatal for long-running programs, and GC can't help while references persist. defined in ch. 14 β open in glossary β for a long-running program (a web server, a database, the OS itself) slow leakage ends in exhaustion and restart. GC languages donβt save you if you still hold a reference: no collector frees reachable memory.
Aside: Why no memory is leaked once your process exits
There are two levels of memory management. Level one: the OS hands memory to processes and takes all of it back at exit β code, stack, and heap pages alike, whatever state your allocator was in. Level two: the malloc library managing your heap within the process. So a short-lived program that never calls free() βworksβ β nothing is truly lost. Bad habit anyway, says the book: aim to free every byte you allocate. And spare a thought for the one program whose leaks nobody cleans up at exitβ¦ the kernel. Those folks have the toughest job of all.5 β Freeing before youβre done: the dangling pointer
Free memory, keep using the old pointer β a
dangling pointer dangling pointer Using memory after freeing it β the region may have been recycled by a later malloc.
defined in ch. 14 β open in glossary
. The next malloc
may recycle that region; your subsequent use crashes the program or
silently overwrites someone elseβs valid data.
6 β Freeing repeatedly: the double free
free(x);
...
free(x); // undefined behavior
The double free double free Freeing the same region twice β undefined behavior, often a crash. defined in ch. 14 β open in glossary β the allocatorβs bookkeeping gets scrambled; crashes are the common outcome. (You met one in the wild already: chapter 10βs List_Pop race manufactured a double free with two threads.)
7 β Calling free() incorrectly
free() accepts exactly one thing: a pointer previously returned by
malloc(). Pass anything else β a stack address, a pointer into the
middle of an allocation β and the invalid free does bad things,
reliably.
Tip: It compiled (or ran) β it is correct
Many events can conspire to make broken code appear to work β until something changes and it stops. The classic student response, βbut it worked before!β, is usually followed by blaming the compiler, the OS, the hardware, or (dare we say) the professor. The problem is almost always where you least want it: in your code. Debug before you blame.Because these errors are so common, an ecosystem of tools grew to hunt them: purify and valgrind are excellent, and once youβve used them youβll wonder how you survived without them (the homework makes sure you find out).
14.5 Underlying OS Support
Notice: no system calls were discussed above. Thatβs because
malloc() and free() are library calls β the malloc library
manages space inside your virtual address space, itself built on real
system calls:
Two levels again: the library asks the OS for wholesale memory via
brk/sbrk (or
mmap() mmap System call that can create anonymous memory regions (swap-backed) usable like a heap; one of the primitives malloc builds on.
defined in ch. 14 β open in glossary
), then retails it to you one malloc at
a time.
14.6 Other Calls
calloc() calloc malloc + zero-fill β inoculates against uninitialized reads. defined in ch. 14 β open in glossary
Allocates and zeroes the memory before returning β a vaccine against specimen #3, the uninitialized read.
realloc() realloc Grows an allocation: new larger region, contents copied, new pointer returned. defined in ch. 14 β open in glossary
Grows an existing allocation: makes a larger region, copies the old contents in, returns the new pointer β the workhorse behind growable arrays.
14.7 Summary
The basics of the memory-allocation API, plus its seven deadly sins. For depth: the C book (K&R) and Stevens β and for how these problems can be found and even corrected automatically, the Exterminator paper. The allocatorβs own inner workings β how the library actually manages that heap β get a whole chapter soon (free-space management territory, chapter 17).
Homework (code): break things, then meet valgrind
Write the bugs on purpose: a NULL dereference (watch it die, then watch it undergdb, then under
valgrind βleak-check=yes); a leak; data[100]
on a 100-int array (does it even crash? is it correct?); a
use-after-free; a free of mid-array. Finish with a realloc-grown
vector and let valgrind referee. The point of it all: knowing your
tools is what makes C survivable. Details at
ostep-homework.Check yourself
1.The buffer-overflow example (malloc(strlen(src)) then strcpy) often runs 'fine.' Why is that the scary part?
2.Why doesn't a garbage collector fully solve memory leaks?
3.A short-lived program exits without ever calling free(). What actually happens to its heap?
4.What makes a dangling pointer dangerous even when the program doesn't crash?
5.Why should you NEVER call brk or sbrk yourself?
6.Which specimen from the gallery does calloc() specifically inoculate against?