Skip to content
FollowtheDamnCode

Debugging

Reading compiler errors, printf debugging, and the bugs C won't catch for you.

8 minute read

Big idea

Bugs are normal. Debugging is a skill: read what the compiler tells you, look at what the program is actually doing, and narrow down until the cause has nowhere to hide.

Read the first error first

A compiler message has a file, a line, a column, and a reason:

main.c:6:23: error: expected ';' after expression
    printf("%d\n", total)
                         ^
                         ;
  • main.c:6:23 — file main.c, line 6, column 23.
  • error: — the program can't be built until this is fixed.
  • The ^ points at where the compiler noticed. The real mistake is often just before it — here, the missing ; at the end of line 6.

One small mistake can cause a cascade of errors below it. Fix the first one, then compile again — the rest often disappear.

Errors you'll see a lot

Message (roughly)Usual cause
expected ';'missing semicolon on that line or the one before
use of undeclared identifier 'totl'a typo, or a variable used outside its scope
implicit declaration of function 'printf'missing #include <stdio.h> (or a misspelt function)
incompatible pointer to integer conversionmixing up p and *p, or forgetting &
undefined reference to 'helper'a linker error: declared, but its .c file wasn't compiled in
expected '}' at end of inputa missing } somewhere above — count your braces

Turn on warnings

Warnings don't stop the build, but most of them are bugs waiting to happen. When you compile yourself, always use:

gcc -Wall -Wextra -g main.c -o main

-Wall -Wextra switch on the useful warnings; -g adds information a debugger needs. Two warnings worth never ignoring:

warning: using the result of an assignment as a condition without parentheses
    if (x = 5)

warning: variable 'sum' is uninitialized when used here
    sum += a[i];

printf debugging

When the output is wrong, print what the program is thinking at each step. This average is wrong — add a printf inside the loop to see why, then fix it:

Fix the bug: the average of 4, 8, 6 should be 6.00
#include <stdio.h>

int main(void) {
    int a[] = {4, 8, 6};
    int n = 3;
    int sum = 0;
    for (int i = 1; i < n; i++) {
        sum += a[i];
    }
    printf("%.2f\n", (double) sum / n);
    return 0;
}
output
6.00

Run and edit it in the app.

A line like printf("i=%d a[i]=%d sum=%d\n", i, a[i], sum); shows the loop never visits i = 0. The loop should start at 0.

Tips that make print-debugging fast:

  • Label everything: printf("after read: n=%d\n", n); beats printf("%d\n", n);.
  • Print at boundaries: entering a function, before and after a loop, just before the crash.
  • Halve the search: put a print in the middle; whichever half is wrong is where to look next.
  • Delete them after — or wrap them in #if DEBUG … #endif.

Watch it

Undefined behaviour: the bugs that hide

Some mistakes don't have a defined result in C. The program may crash, print garbage, or — worst of all — seem to work on your machine and fail on someone else's. These are undefined behaviour (UB). The big four:

1. Out of bounds — reading or writing past the end of an array:

int a[5];
for (int i = 0; i <= 5; i++) a[i] = 0;   // ✗ a[5] doesn't exist; use i < 5

2. Uninitialised variables — reading a local before giving it a value:

int count;          // ✗ holds whatever was left in that memory
count++;            // ✓ fix: int count = 0;

3. Use after free — touching memory you've given back:

free(p);
printf("%d\n", *p); // ✗ p is dangling
p = NULL;           // ✓ set it to NULL right after free

4. NULL dereference — following a pointer that points nowhere:

FILE *f = fopen("data.txt", "r");
fgets(line, 100, f);  // ✗ if fopen failed, f is NULL — check first

Because UB can "work" by luck, testing alone won't catch it. Two habits will: initialise every variable when you declare it, and check every pointer before you use it. Tools help too — compiling with -fsanitize=address,undefined makes most of these crash loudly at the exact line.

Predict: = vs ==
#include <stdio.h>

int main(void) {
    int lives = 3;
    if (lives = 0) {
        printf("Game over\n");
    } else {
        printf("Still playing, lives=%d\n", lives);
    }
    return 0;
}
What does it print? Show the answer
Still playing, lives=0

Run and edit it in the app.

lives = 0 assigns 0, and the condition's value is that 0 — false. The else branch runs, and lives has been wiped. -Wall warns about exactly this.

Your program produces 14 compiler errors. What should you do first?

A debugging routine

  1. Reproduce it. Find an input that fails every time.
  2. Read the error or the wrong output carefully. What exactly is wrong?
  3. Guess where the bug could be.
  4. Look: print the values there (or step through it in a debugger like gdb or lldb).
  5. Fix one thing, and re-run the failing input — and the ones that used to work.

Think of it like

A detective, not a gambler. Changing random lines and re-running is guessing. Gathering clues (prints, messages), forming a theory, and testing it is how you actually close the case.

Key terms

  • Compile error — Code the compiler can't translate; nothing runs until it's fixed.
  • Warning — Code that compiles but looks wrong. Usually it is.
  • Linker error — Everything compiled, but a function's definition can't be found.
  • printf debugging — Printing values to see what the program is really doing.
  • Undefined behaviour (UB) — Code with no defined result: out of bounds, uninitialised reads, use after free, NULL dereference.

Check yourself

In main.c:12:5: error: … , what is 12?

Which of these is undefined behaviour?

What does -Wall do?