C++ Debugging Practical Complete Guide | gdb, LLDB, Visual Studio Complete Usage

C++ Debugging Practical Complete Guide | gdb, LLDB, Visual Studio Complete Usage

이 글의 핵심

Organizes core concepts and practical points of C++ debugging practical guide.

1. cout Debugging

Below is an implementation example using C++. Import the necessary modules. Understand the role of each part while examining the code.

#include <iostream>
using namespace std;
int buggyFunction(int x) {
    cout << "[DEBUG] buggyFunction called, x = " << x << endl;
    
    int result = x * 2;
    cout << "[DEBUG] result = " << result << endl;
    
    return result;
}

Pros: Simple, fast
Cons: Requires code modification, performance impact

2. Check Conditions with assert

Below is an implementation example using C++. Import the necessary modules. Understand the role of each part while examining the code.

#include <cassert>
void process(int* ptr) {
    assert(ptr != nullptr);  // Check only in debug build
    assert(*ptr > 0);
    
    // ...
}
int main() {
    int* ptr = nullptr;
    process(ptr);  // Assertion fails!
}

3. Using Breakpoints

Visual Studio

Below is an implementation example using code. Try running the code directly to check its operation.

F9: Set/unset breakpoint
F5: Start debugging
F10: Step over (execute line by line)
F11: Step into (enter function)
Shift+F11: Step out (exit function)

GDB

Here is detailed implementation code using bash. Understand the role of each part while examining the code.

# Compile (with -g option required)
g++ -g program.cpp -o program
# Run gdb
gdb ./program
# Set breakpoint
(gdb) break main
(gdb) break file.cpp:42
# Run
(gdb) run
# Step by step
(gdb) next  # Step Over
(gdb) step  # Step Into
# Print variable
(gdb) print variable
(gdb) print *ptr
# Continue execution
(gdb) continue

4. Conditional Breakpoints

Visual Studio

Right-click breakpoint → Conditions
Example: i == 100

GDB

(gdb) break main if x == 10
(gdb) condition 1 i == 100  # Add condition to breakpoint 1

5. Memory Debugging

Valgrind (Linux)

Below is an implementation example using bash. Understand the role of each part while examining the code.

# Install
sudo apt install valgrind
# Check memory leaks
valgrind --leak-check=full ./program
# Output example
==12345== HEAP SUMMARY:
==12345==     in use at exit: 40 bytes in 1 blocks
==12345==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==12345== 
==12345== LEAK SUMMARY:
==12345==    definitely lost: 40 bytes in 1 blocks

AddressSanitizer

Below is an implementation example using bash. Try running the code directly to check its operation.

# Compile
g++ -fsanitize=address -g program.cpp -o program
# Run
./program
# Auto-detect errors
=================================================================
==12345==ERROR: AddressSanitizer: heap-use-after-free

Understanding with everyday analogy: Think of memory as an apartment building. Stack is like an elevator - fast but limited space. Heap is like a warehouse - spacious but takes time to find things. Pointers are like notes saying “Floor 3, Unit 302” - they point to addresses.

Practical Examples

Example 1: Debugging Segmentation Fault

Here is detailed implementation code using C++. Import the necessary modules and perform branching with conditionals. Understand the role of each part while examining the code.

#include <iostream>
using namespace std;
int main() {
    int* ptr = nullptr;
    
    // Debugging tip: Check nullptr
    if (ptr == nullptr) {
        cerr << "Error: ptr is nullptr!" << endl;
        return 1;
    }
    
    *ptr = 10;  // Segfault!
    
    return 0;
}

Debugging with GDB: Below is an implementation example using bash. Try running the code directly to check its operation.

$ gdb ./program
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
(gdb) backtrace  # Check call stack
(gdb) print ptr  # Check ptr value
$1 = (int *) 0x0

Example 2: Debugging Infinite Loop

Below is an implementation example using C++. Import the necessary modules and process data with loops. Understand the role of each part while examining the code.

#include <iostream>
using namespace std;
int main() {
    int i = 0;
    
    while (i < 10) {
        cout << i << endl;
        // i++; Missing! Infinite loop
    }
    
    return 0;
}

Debugging method:

  1. Stop with Ctrl+C
  2. Set breakpoint
  3. Check variable i value
  4. Check if i++ executes

Example 3: Log Macro

Here is detailed implementation code using C++. Import the necessary modules. Understand the role of each part while examining the code.

#include <iostream>
using namespace std;
#ifdef DEBUG
#define LOG(msg) cout << "[" << __FILE__ << ":" << __LINE__ << "] " << msg << endl
#else
#define LOG(msg)
#endif
int main() {
    int x = 10;
    LOG("x = " << x);
    
    x *= 2;
    LOG("x after multiply = " << x);
    
    return 0;
}

Compile:

# Debug build
g++ -DDEBUG program.cpp -o program
# Release build (no logs)
g++ program.cpp -o program

Summary

Key Points

  1. cout debugging: Simple but requires code modification
  2. assert: Check conditions in debug build
  3. Breakpoints: Pause execution and inspect state
  4. Memory debugging: Use Valgrind or AddressSanitizer
  5. Conditional breakpoints: Break only when condition met

Debugging Tools

Visual Studio:

  • Integrated debugger
  • Easy to use
  • Windows-friendly ✅ GDB/LLDB:
  • Command-line debuggers
  • Powerful and flexible
  • Linux/Mac standard ✅ AddressSanitizer:
  • Fast memory error detection
  • Compile-time instrumentation
  • Recommended for daily use

Best Practices

  • ✅ Compile with debug symbols (-g)
  • ✅ Use assertions for preconditions
  • ✅ Learn debugger keyboard shortcuts
  • ❌ Don’t leave debug code in production
  • ❌ Don’t ignore warnings