Pitfalls & Debugging
The mistakes every C programmer makes once, and how to catch them.
C's compiler will happily compile code that has serious problems — this chapter is a field guide to the mistakes almost everyone makes at least once, and the tools that catch them.
The recurring theme: undefined behavior
Many C mistakes don't produce an error — they produce undefined behavior, where the language spec makes no promises about what happens next. Sometimes it crashes. Sometimes it silently produces the wrong answer. Sometimes it appears to work correctly, until you change an unrelated part of the program and it stops. Every pitfall below is a specific case of this.
Turn on warnings — and read them
1gcc -Wall -Wextra program.c -o program
-Wall -Wextra catches a large fraction of common mistakes at compile time: unused variables, mismatched printf specifiers, comparisons that are always true, and more. There's little reason not to compile with these flags enabled from day one.
Uninitialized variables
1int total; // holds garbage2printf("%d\n", total); // unpredictable output
Always give local variables an initial value before reading them.
Off-by-one errors
1int arr[5];2for (int i = 0; i <= 5; i++) { // should be i < 53arr[i] = 0; // writes one past the end on the last iteration4}
Comparing floating-point numbers with ==
1float a = 0.1f + 0.2f;2if (a == 0.3f) { ... } // often false — floating-point rounding
Compare against a small tolerance instead: fabs(a - 0.3f) < 0.0001.
Memory mistakes
- Memory leak —
mallocwithout a matchingfree. - Use-after-free — dereferencing a pointer after it's been freed.
- Double free — calling
freetwice on the same pointer. - Buffer overflow — writing past the end of an array or allocated block.
valgrind (Linux/macOS) runs your program and reports memory leaks and invalid accesses. Compiling with -fsanitize=address builds similar checks directly into the executable. Both are worth learning early — they turn silent, intermittent bugs into a clear error message pointing at the exact line.A minimal debugging checklist
- Compile with
-Wall -Wextraand fix every warning. - Add
printfstatements to check values at key points. - Isolate the smallest program that still reproduces the bug — it's far easier to reason about ten lines than a thousand.
- Check every array index, every pointer for
NULL, and everymallocresult before trusting it.