Debugging C++ — Techniques
C++ debugging techniques — sanitizers, print debugging, assertions, core dumps, watchpoints, and debugging optimized code.
Compile-Time Debugging
cpp
// static_assert — catch errors before running
static_assert(sizeof(MyStruct) == 24, "size changed — check padding");
static_assert(std::is_trivially_copyable_v<Packet>);
// constexpr evaluation — test at compile time
constexpr auto result = my_function(42);
static_assert(result == expected_value);
// type printing trick (works via error message)
template<typename T> struct ShowType;
ShowType<decltype(my_var)>{}; // error: incomplete type ShowType<int> — reveals typeSanitizers (Best First Line of Defense)
bash
# AddressSanitizer — detects: use-after-free, buffer overflow, use-after-scope
clang++ -fsanitize=address -g -O1 myapp.cpp
# UndefinedBehaviorSanitizer — detects: signed overflow, null deref, misaligned access
clang++ -fsanitize=undefined -g myapp.cpp
# ThreadSanitizer — detects: data races, deadlocks
clang++ -fsanitize=thread -g -O1 myapp.cpp
# LeakSanitizer — detects: memory leaks (built into ASAN)
ASAN_OPTIONS=detect_leaks=1 ./myapp
# MemorySanitizer — detects: use of uninitialized values (Clang only)
clang++ -fsanitize=memory -g myapp.cpp
# Combine (except thread + address):
clang++ -fsanitize=address,undefined -g -O1 myapp.cppPrint Debugging
cpp
// Structured debug print
#ifdef DEBUG
#define DPRINT(x) std::println("[{}:{}] {} = {}", __FILE__, __LINE__, #x, (x))
#else
#define DPRINT(x)
#endif
DPRINT(some_variable); // [main.cpp:42] some_variable = 17
// Debug-only code block
#ifdef NDEBUG
// release
#else
// debug
#endif
// C++20 source_location for logging without macros
void log(auto value, std::source_location loc = std::source_location::current()) {
std::println("[{}:{}] {}", loc.file_name(), loc.line(), value);
}Core Dumps (Linux)
bash
# Enable core dumps (disabled by default)
ulimit -c unlimited
# Run the crashing program
./myapp # → "Segmentation fault (core dumped)" → core file created
# Analyze with GDB
gdb ./myapp core
(gdb) bt # backtrace
(gdb) frame 3 # select frame 3
(gdb) info locals # see local variables
(gdb) print var # print a variable
# Set core dump location (systemd)
echo '/tmp/core.%e.%p' | sudo tee /proc/sys/kernel/core_patternGDB Quick Commands
cpp
gdb ./myapp
break main set breakpoint at main
break file.cpp:42 breakpoint at line 42
run arg1 arg2 run program
continue (c) continue execution
next (n) next line (step over)
step (s) step into
finish run until current function returns
bt backtrace (call stack)
frame N select frame N
info locals local variables in current frame
print expr print expression
watch var watchpoint: stop when var changes
x/10x $rsp examine 10 hex words at stack pointer
thread apply all bt backtrace all threads
quit exit GDBDebugging Optimized Code
cpp
// Problem: optimizer removes variables and reorders code
// -O2 makes values "optimized out" in the debugger
// Use __attribute__((optimize("O0"))) on specific functions (GCC/Clang)
[[gnu::optimize("O0")]] // debug this function even at -O2
void debug_this() {
// variables visible in debugger
}
// Or compile specific .cpp with -O0 via CMake:
// set_source_files_properties(suspect.cpp PROPERTIES COMPILE_FLAGS "-O0 -g")
// Prevent optimization of a variable (barrier for debugging)
volatile int debug_val = suspicious_result;
// The compiler can't optimize away the read, so you can inspect itValgrind
bash
# Memory error detection (slower than ASan but no recompile needed)
valgrind --tool=memcheck ./myapp
# Detailed leak check
valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./myapp
# Cache profiling
valgrind --tool=cachegrind ./myapp
cg_annotate cachegrind.out.PID
# Call graph
valgrind --tool=callgrind ./myapp
kcachegrind callgrind.out.PID # GUI visualizationCommon Bug Patterns
bash
# Segfault at startup: usually static initialization order fiasco
# → Add -Wl,-z,defs to catch undefined symbols
# → Use local static / Meyers singleton
# Random crashes: usually memory corruption
# → Run with -fsanitize=address first
# → Look for buffer overflows, use-after-free, stack corruption
# Deadlock: program hangs
# → gdb + "thread apply all bt" to see where all threads are waiting
# → Run with -fsanitize=thread
# Data race: incorrect output, nondeterministic
# → -fsanitize=thread
# Slow performance: profile before optimizing
# → perf stat ./myapp (hardware counters)
# → perf record + perf report (sampling)
# → valgrind --tool=cachegrind (cache misses)Compile Flags for Debugging
cmake
# CMake: debug build
cmake -DCMAKE_BUILD_TYPE=Debug ..
# Add sanitizers in CMake
target_compile_options(myapp PRIVATE
-g3 # maximum debug info (includes macros)
-fno-omit-frame-pointer # reliable backtraces
-fsanitize=address,undefined
)
target_link_options(myapp PRIVATE -fsanitize=address,undefined)
# Enable extra warnings (catch bugs at compile time)
target_compile_options(myapp PRIVATE
-Wall -Wextra -Wpedantic
-Wshadow -Wconversion -Wsign-conversion
-Wunused -Wnull-dereference
)