Debugging C++ Code: 8 Techniques That Will Save Your Sanity

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Debugging C++ Code: How to Stop Fearing Segmentation Fault and Love GDB



Have you ever spent three hours searching for a bug that turned out to be a forgotten break in a switch or an extra ; after an if? If so — welcome to the club. Debugging C++ is an art bordering on masochism. But, like any art, it can be mastered.



In this article, we will cover not just "how to run GDB", but real techniques that turn debugging from reading tea leaves into Sherlock Holmes detective work. We'll talk about modern sanitizers, reverse debugging, and even how to catch a bug that only reproduces "on Friday after lunch".



1. The Classics: GDB and LLDB



Let's start with the fundamentals. The GNU Debugger (GDB) and its competitor LLDB are the Swiss Army knives of debuggers. But most people use only 10% of their capabilities. Let's fix that.



Conditional Breakpoints



Do you set a breakpoint in a loop of 10,000 iterations and press continue 9,999 times? Stop. Use conditional breakpoints:



(gdb) break main.cpp:42 if i == 5000


This will save you hours of your life. In LLDB, the syntax is similar:



(lldb) breakpoint set --file main.cpp --line 42 --condition 'i == 5000'


Watchpoints — Catching Variable Changes



Imagine you have a variable that changes somewhere unknown. Set a watchpoint:



(gdb) watch my_variable(gdb) rwatch my_variable   # triggers on read(gdb) awatch my_variable   # triggers on both read and write


Now the program will stop exactly when someone touches your variable. Magic, right?



2. Reverse Debugging: Time Travel



A programmer's worst nightmare: the program crashed, but you don't know how it got to that state. Reverse debugging allows you to "rewind" execution backwards.



In GDB, you need record support:



(gdb) record(gdb) continue... program crashes ...(gdb) reverse-step   # step backwards(gdb) reverse-continue # continue in reverse


In commercial tools (e.g., UndoDB or Mozilla's rr), this works even better. rr (Record and Replay) is a free tool that records program execution and then lets you replay it over and over:



$ rr record ./my_program$ rr replay(gdb) continue... found the crash site ...(gdb) reverse-step


Tip: use rr for finding data races — it deterministically reproduces every execution.



3. Sanitizers: A Vaccine Against Undefined Behavior



Modern C++ (starting with Clang and GCC 4.8+) has built-in tools our fathers could only dream of. AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), and ThreadSanitizer (TSan) are must-haves.



Example: Catching a Memory Leak



// compile with: g++ -fsanitize=address -g main.cpp -o main#include <iostream>

void leak() { int* p = new int(42); // forgot delete}

int main() { leak(); return 0;}


Run it — and we see:



==12345==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4 byte(s) in 1 object(s) allocated from: #0 0x7f... in operator new(unsigned long) #1 0x400... in leak() main.cpp:5 #2 0x400... in main main.cpp:10


ASan will show the exact call stack, including the line number where the allocation occurred. No more "memory is leaking somewhere".



UBSan Against Undefined Behavior



// compile with: g++ -fsanitize=undefined -g main.cppint main() {    int x = INT_M                

Blogs

Book Recommendations