$ overview
overview/0x10·Onward

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

terminal
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

uninitialized.c
1int total; // holds garbage
2printf("%d\n", total); // unpredictable output

Always give local variables an initial value before reading them.

Off-by-one errors

off-by-one.c
1int arr[5];
2for (int i = 0; i <= 5; i++) { // should be i < 5
3 arr[i] = 0; // writes one past the end on the last iteration
4}

Comparing floating-point numbers with ==

float-compare.c
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 — malloc without a matching free.
  • Use-after-free — dereferencing a pointer after it's been freed.
  • Double free — calling free twice on the same pointer.
  • Buffer overflow — writing past the end of an array or allocated block.
note: Tools that catch these automatically
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 -Wextra and fix every warning.
  • Add printf statements 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 every malloc result before trusting it.