A debugger runs your program one line at a time and lets you look at every variable on the way. StepVioz's visualizer is a debugger that also draws memory. On your own computer, the standard C debugger is gdb (on macOS, lldb works almost the same way).
A bug to find
#include <stdio.h>
double average(int a[], int n) {
int total = 0;
for (int i = 0; i < n; i++) {
total += a[i];
}
return total / n;
}
int main(void) {
int marks[] = {70, 85, 90, 64};
printf("average %.2f\n", average(marks, 4));
return 0;
}The marks add up to 309, and 309 / 4 is 77.25, but the program prints 77.00. Step through it in the visualizer, and watch total and n in average.
The same search in gdb
First compile with -g (keep the names and line numbers) and -O0 (no optimizing, so the lines run in order):
gcc -g -O0 average.c -o average
gdb ./average
(gdb) break average stop when average is called
(gdb) run
Breakpoint 1, average (a=0x7ffd..., n=4) at average.c:4
(gdb) next run one line
(gdb) next
(gdb) print total
$1 = 309
(gdb) print total / n
$2 = 77
total / n divides two ints, so C throws the fraction away before the result becomes a double. The fix is to make one side a double:
#include <stdio.h>
double average(int a[], int n) {
int total = 0;
for (int i = 0; i < n; i++) {
total += a[i];
}
return (double)total / n;
}
int main(void) {
int marks[] = {70, 85, 90, 64};
printf("average %.2f\n", average(marks, 4));
return 0;
}The commands you need
| Command | Short | What it does |
|---|---|---|
break average / break average.c:6 | b | stop at a function or a line |
run | r | start the program (run < input.txt gives it input) |
next | n | run one line; a function call counts as one line |
step | s | run one line, going into a function call |
print total | p | show a value, or any expression |
info locals | show every local variable | |
backtrace | bt | show the chain of calls that led here |
watch total | stop whenever total changes | |
continue | c | run until the next breakpoint |
quit | q | leave gdb |
When a program crashes with Segmentation fault, run it inside gdb: it stops at the crash, and backtrace shows which line of which function was running.
Finding memory bugs
- AddressSanitizer: compile with
-fsanitize=address -g(gcc or clang on Linux and macOS). Reading outside an array or using freed memory stops the program with the line number. - Valgrind (Linux):
valgrind --leak-check=full ./appreports leaks: blocks frommallocthat were never freed.
The method is the same whatever the tool: reproduce the bug, guess where it is, look at the values there, and change one thing at a time.
Quick check
Practice problems for this lesson
My notes
Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.