Skip to content

Expert · Debugging and tools · topic 55 of 60 · 20 min

A method for debugging

Explanation

Good debugging is a method, not luck: reproduce the bug reliably, shrink it to the smallest failing case, read every error and warning, form a hypothesis, test it with a tool (gdb, sanitizers, prints), fix the cause rather than the symptom, and add a test. Most C crashes come from a short list of causes worth checking first.

The steps

  1. Reproduce: find exact input and steps that make the bug appear every time. A bug you cannot reproduce cannot be confirmed fixed.
  2. Minimise: cut the input and the code down until removing anything more makes the bug disappear. Small cases make the cause obvious.
  3. Read: compiler warnings, sanitizer reports, the backtrace, the logs. The answer is often already printed.
  4. Hypothesise: state a specific guess ("count is negative because the loop runs one extra time").
  5. Test the hypothesis: a breakpoint, a watchpoint, a print, an assert. Confirm or reject it with evidence.
  6. Fix the cause, not the symptom: adding if (p == NULL) return; where it crashes may hide why p was NULL.
  7. Add a test that fails without the fix, and run the full test suite (with sanitizers).

The usual suspects for C crashes

SymptomCheck first
Segmentation faultNULL pointer, uninitialised pointer, out-of-bounds index, use after free, stack overflow from deep recursion
Garbage valuesUninitialised variables, missing return value, wrong printf format (%d for a long or double)
Works in debug, fails with -O2Undefined behaviour (signed overflow, strict aliasing, uninitialised reads)
Random crashes far from the bugEarlier buffer overflow or double free corrupting memory
Strings print extra junkMissing \0 terminator, buffer too small
Memory grows over timeLeaks on some code path (often an error path)
Infinite loopUnsigned counter compared with >= 0, loop variable never changed, stray ; after while

Printf debugging, done well

c
fprintf(stderr, "[%s:%d] i=%d len=%zu ptr=%p\n", __FILE__, __LINE__, i, len, (void *) ptr);
Print to stderr (unbuffered, so the line appears even if the program crashes right after), include the location, and use the correct formats. Remove or disable these lines afterwards.

Rubber-duck debugging

Explaining the code line by line to someone else, or even to a rubber duck on your desk, forces you to state what each line really does rather than what you meant it to do. Surprisingly often, the bug becomes obvious halfway through the explanation.

Careful: Changing random things until the bug "goes away" is not debugging. In C, the bug usually just moves: adding a printf can shift memory layout so an overflow corrupts something else. Only a fix you can explain is a real fix.
Think of it like this: Debugging is like a doctor's diagnosis: take the history (reproduce), run focused tests (tools), form a hypothesis about the disease, and treat the cause, not just the fever. A painkiller (hiding the crash) makes the patient feel better while the illness continues.
Tip: Keep a personal "bug diary": the symptom, the cause and how you found it. Patterns appear quickly, and it is excellent material for interview stories.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

A method for debugging · 1/6

A debugging method

In this lesson you will learn a reliable method for finding bugs.

Prefer a video by a teacher? Find free videos on “how to debug c programs systematically segmentation fault causes” on YouTube ↗

In real life: The crash that only happened on Tuesdays

A depot's bus-schedule program crashed only on some days. The developer reproduced it by setting the date, then minimised the input to one route with 31 stops. AddressSanitizer reported a stack-buffer-overflow: the stops array had 30 slots, and only Tuesday's timetable had a 31-stop route. The fix sized the array from the data (malloc) and added a test with 31 stops.

c
/* before */  char stop_names[30][40];          /* fixed at 30 */
/* after  */  char (*stop_names)[40] = malloc((size_t) n_stops * sizeof *stop_names);

Practice (0/3 done)

Task 1 · Question

A program works with -O0 but prints wrong totals with -O2. What should you suspect first?

Task 2 · Question

A string prints with extra strange characters at the end. Most likely cause?

Task 3 · Practice

Pick a real bug you hit during this course (or ask a friend for a buggy C program). Write a short report: the symptom, how you reproduced it, the minimised case, the tools you used and what they showed, your hypothesis, the fix, and the test you added.

Quick quiz

Question 1

1.What is the first step in debugging?

Question 2

2.Why print debug output to stderr rather than stdout?

Question 3

3.What does "fix the cause, not the symptom" mean?

Question 4

4.Which is a common cause of an infinite loop in C?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is the first step in debugging?

Answer: Reproduce the bug reliably

You cannot confirm a fix otherwise.

Why print debug output to stderr rather than stdout?

Answer: stderr is unbuffered, so lines appear even if the program crashes right after

Buffered stdout may be lost on a crash.

What does "fix the cause, not the symptom" mean?

Answer: Find why the bad value appeared instead of just guarding where it crashes

Guards can hide the real problem.

Which is a common cause of an infinite loop in C?

Answer: An unsigned counter compared with >= 0

An unsigned value is always >= 0.

What is the null hypothesis in an A/B test of two page designs?Related

Answer: There is no difference between the designs

H0 is the "nothing is happening" claim that we try to find evidence against.

From Hypothesis tests, p-values and A/B testing

Which flag adds debug information for gdb?Related

Answer: -g

Line numbers and variable names.

From Debugging with gdb

Which is a common cause of janky Compose screens?Related

Answer: Heavy work like sorting or formatting inside composable bodies

Move work to the ViewModel or remember it.

From Debugging and performance

Which sanitizer finds use-after-free bugs?Related

Answer: AddressSanitizer

AddressSanitizer checks memory accesses.

From Building real projects: CMake, warnings, debuggers and sanitizers

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Debugging and tools”. The module exam comes next.
  2. Module 11 exam: Debugging and tools · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Embedded C, system calls and careers. 60 topics left in the course before the final exam.

← Previous: assert, logging and testing C code