C++ Debugging
GDB Basics
Compile with debug info and run under gdb
g++ -g -O0 -o myapp main.cpp
gdb ./myapp
Essential GDB commands
run # start execution
run arg1 arg2 # start with arguments
break main # breakpoint at function
break main.cpp:42 # breakpoint at line
break Host::address # breakpoint at method
next # step over (don't enter functions)
step # step into (enter functions)
continue # resume execution
print var # print variable value
print *ptr # dereference pointer
print vec.size() # call member function
display var # print var after every step
backtrace # show call stack
frame 3 # switch to stack frame 3
info locals # show local variables
info breakpoints # list breakpoints
delete 1 # delete breakpoint 1
watch var # break when var changes
quit # exit
Valgrind
Detect memory errors
# Memory leak detection
valgrind --leak-check=full ./myapp
# Track origins of uninitialized values
valgrind --track-origins=yes ./myapp
# Output:
# ==12345== LEAK SUMMARY:
# ==12345== definitely lost: 48 bytes in 1 blocks
# ==12345== indirectly lost: 0 bytes in 0 blocks
Valgrind runs the binary in a virtual machine — ~10-20x slowdown but catches use-after-free, double-free, buffer overflows, and leaks.
Assertions
Runtime checks for invariants
#include <cassert>
void process(int* data, size_t size) {
assert(data != nullptr && "data must not be null");
assert(size > 0 && "size must be positive");
// process...
}
// static_assert — compile-time check
static_assert(sizeof(int) == 4, "int must be 4 bytes");
static_assert(std::is_move_constructible_v<Host>, "Host must be movable");
assert is removed in release builds (-DNDEBUG). static_assert runs at compile time — no runtime cost.
Preprocessor Debugging
Conditional compilation for debug output
#ifdef DEBUG
#define LOG(msg) std::cerr << "[DEBUG] " << msg << std::endl
#else
#define LOG(msg)
#endif
LOG("entering function"); // only in debug builds
// Compile with: g++ -DDEBUG -o app main.cpp
Prefer constexpr if or logging libraries over preprocessor macros in modern C++. Macros bypass the type system and are harder to debug.
Core Dumps
Analyze crashes post-mortem
# Enable core dumps
ulimit -c unlimited
# Run the crashing program
./myapp
# Segmentation fault (core dumped)
# Analyze
gdb ./myapp core
# In gdb:
# backtrace # see where it crashed
# info registers # register state at crash
# print *ptr # examine the offending pointer
Core dumps capture the entire process state at the time of crash. backtrace shows the call stack leading to the fault.