Debugging with gdb

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;
}
Open in compiler

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;
}
Open in compiler

The commands you need

CommandShortWhat it does
break average / break average.c:6bstop at a function or a line
runrstart the program (run < input.txt gives it input)
nextnrun one line; a function call counts as one line
stepsrun one line, going into a function call
print totalpshow a value, or any expression
info localsshow every local variable
backtracebtshow the chain of calls that led here
watch totalstop whenever total changes
continuecrun until the next breakpoint
quitqleave 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 ./app reports leaks: blocks from malloc that 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

1. Why compile with -g before using gdb?
2. What is the difference between next and step?
3. int total = 309, n = 4; what is total / n?

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.