CppDebugging (gdb)

Debugging (gdb)

Sprinkling std::cout statements through your code to see what's happening is a debugging technique everyone reaches for — but it has real limits. You have to guess where to put each print in advance, recompile every time you want to check something new, and it clutters real production code if you forget to remove it. A real debugger lets you pause a running program and inspect it interactively instead.
Compiling with debug symbols
A debugger needs to map machine instructions back to your source lines and variable names. That mapping only exists if you compile with the -g flag, which embeds debug symbols in the binary.

terminal

Bash
g++ -g -o app main.cpp
gdb basics
The GNU Debugger, gdb, is the standard debugger on Linux (and available via ports elsewhere). It's a command-line tool with a small set of commands that cover most day-to-day debugging needs.

Command

What it does

break <location>

Set a breakpoint at a line or function name (e.g. break main.cpp:12 or break computeTotal).

run

Start (or restart) the program under the debugger.

next

Execute the current line, stepping over function calls.

step

Execute the current line, stepping into function calls.

continue

Resume running until the next breakpoint (or the program ends).

print <expr>

Print the current value of a variable or expression.

backtrace

Show the current call stack — which function called which.

A worked debugging session

main.cpp

CPP
#include <iostream>

int computeTotal(int a, int b) {
  int sum = a + b;
  return sum;
}

int main() {
  int x = 5;
  int y = 10;
  int total = computeTotal(x, y);
  std::cout << "Total: " << total << "\n";
  return 0;
}

terminal

Bash
g++ -g -o app main.cpp
gdb ./app

Inside the gdb prompt, a typical session looks like this:

(gdb) break computeTotal
Breakpoint 1 at 0x1189: file main.cpp, line 4.

(gdb) run
Starting program: ./app

Breakpoint 1, computeTotal (a=5, b=10) at main.cpp:4
4         int sum = a + b;

(gdb) print a
$1 = 5

(gdb) next
5         return sum;

(gdb) print sum
$2 = 15

(gdb) backtrace
#0  computeTotal (a=5, b=10) at main.cpp:4
#1  main () at main.cpp:11

(gdb) continue
Continuing.
Total: 15
[Inferior 1 (process 1234) exited normally]
The breakpoint pauses execution the moment computeTotal is entered. From there, print inspects the actual runtime values of a and b — no recompiling, no guessing — and backtrace confirms exactly which call led here.
Most developers use gdb through an IDE
Note
In day-to-day work, very few developers type raw gdb commands like the ones above. VS Code, CLion, and Visual Studio all wrap gdb (or LLDB on macOS) with a graphical interface: click a line number to set a breakpoint, hover a variable to see its value, and use step/continue buttons instead of typing commands. Learning the underlying commands is still valuable — it's what makes those buttons work, and it's essential when you only have a terminal (e.g. debugging on a remote server).
  • Compile with -g so the debugger can map machine code back to your source.

  • break, run, next, step, continue control execution flow.

  • print inspects variables; backtrace shows the call stack.

  • IDEs like VS Code, CLion, and Visual Studio give gdb/lldb a graphical front end — that is how most developers actually use it.