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— filemain.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 conversion | mixing 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 input | a 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:
#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.00Run 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);beatsprintf("%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.
#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=0Run 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
- Reproduce it. Find an input that fails every time.
- Read the error or the wrong output carefully. What exactly is wrong?
- Guess where the bug could be.
- Look: print the values there (or step through it in a debugger like
gdborlldb). - 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?