C++ Notes: Debugging Techniques for Visual Studio and VS Code – A Complete Guide to IDE Debuggers

1123123123123

Introduction

Debugging is a core skill that every C++ developer must master. Statistics show that developers spend 60% of their time debugging code, and proficient use of IDE debuggers can increase debugging efficiency by over 300%. Whether leveraging the powerful features of Visual Studio or the lightweight flexibility of VS Code, mastering the correct debugging methods can liberate you from the inefficiency of “printf debugging.”

Learning Objectives

  • • Master the debugging environment configuration for Visual Studio and VS Code
  • • Learn to use core debugging features such as breakpoints, variable watches, and call stacks
  • • Understand advanced debugging techniques: conditional breakpoints, exception breakpoints, and memory debugging
  • • Master debugging methods for common bugs through practical case studies

Basic Debugging Concepts

What is a Debugger

Concept Explanation:A debugger is a tool that helps developers inspect and modify the runtime state of a programWhy It Matters:It allows real-time viewing of variable values, controlling program execution flow, and locating bug positionsApplicable Scenarios:Program crash analysis, logic error localization, performance issue troubleshooting

Debugging vs. Printf Debugging

Comparison Item Printf Debugging IDE Debugger
Efficiency Low (requires recompilation) High (real-time interaction)
Information Volume Limited (preset output) Rich (full program state)
Interactivity None Strong (pause, step, modify)
Learning Cost Very Low Medium
Applicable Scenarios Simple logic validation Complex bug localization

Best Practice:Use printf for simple issues, but for complex issues, a debugger is a must

Visual Studio Debugging Guide

Environment Configuration

Basic Configuration Requirements

// Ensure compilation configuration is correct
// Project Properties -> C/C++ -> Optimization -> Disable Optimization (/Od)
// Project Properties -> C/C++ -> Debug Information Format -> Program Database (/Zi)
// Project Properties -> Linker -> Debug -> Generate Debug Info -> Yes

Example Program:Student Grade Management System

#include <iostream>
#include <vector>
#include <string>

class StudentManager {
private:
    std::vector<std::pair<std::string, int>> students;
    
public:
    void addStudent(const std::string& name, int score) {
        students.push_back({name, score});
    }
    
    double getAverageScore() {
        if (students.empty()) return 0.0;  // Potential bug point
        
        int total = 0;
        for (const auto& student : students) {
            total += student.second;
        }
        return static_cast<double>(total) / students.size();
    }
    
    std::string getTopStudent() {
        if (students.empty()) return "";
        
        auto maxIt = students.begin();
        for (auto it = students.begin(); it != students.end(); ++it) {
            if (it->second > maxIt->second) {
                maxIt = it;
            }
        }
        return maxIt->first;
    }
};

int main() {
    StudentManager manager;
    manager.addStudent("张三", 85);
    manager.addStudent("李四", 92);
    
    std::cout << "Average Score: " << manager.getAverageScore() << std::endl;
    std::cout << "Top Student: " << manager.getTopStudent() << std::endl;
    return 0;
}

Basic Debugging Operations

Setting and Managing Breakpoints

Three Methods to Set Breakpoints

  1. 1. Line Breakpoint:Click to the left of the line number or press F9
  2. 2. Function Breakpoint:Debug -> Windows -> Breakpoints -> New -> Function Breakpoint
  3. 3. Data Breakpoint:Right-click variable -> Break when data changes

Practical Demonstration

double getAverageScore() {
    if (students.empty()) return 0.0;  // ← Set Breakpoint 1: Check boundary condition
    
    int total = 0;                     // ← Set Breakpoint 2: Observe initial state
    for (const auto& student : students) {
        total += student.second;       // ← Set Breakpoint 3: Monitor accumulation process
    }
    return static_cast<double>(total) / students.size();  // ← Breakpoint 4: Validate calculation result
}

Debugging Shortcut Keys

Visual Studio

  • F5:Start Debugging
  • F9:Set/Delete Breakpoint
  • F10:Step Over (Skip Function)
  • F11:Step Into (Enter Function)
  • Shift+F11:Step Out of Current Function
  • Ctrl+F5:Start Without Debugging
  • Shift+F5:Stop Debugging

VS Code

  • F5:Start Debugging
  • F9:Toggle Breakpoint
  • F10:Step Over
  • F11:Step Into
  • Shift+F11:Step Out
  • Ctrl+Shift+F5:Restart Debugging
  • Shift+F5:Stop Debugging

Variable Watch Techniques

1. Automatic Watch

  • • Hover over a variable to view its value
  • • The “📌” icon next to the variable name can pin the display

2. Watch Window

// Add expressions in the watch window
students.size()           // View container size
students[0].first        // View first student's name
total / students.size()  // View division result
&students               // View memory address

3. Immediate Window (Ctrl+Alt+I)

// Execute any expression during debugging
? students.size()        // Quickly calculate expression
? students.empty()       // Check container status
students.push_back({"Test", 100})  // Even modify program state!

Advanced Debugging Features

Conditional Breakpoints

Usage Scenario:Stop only under specific conditions in a loop

Setting Method

  1. 1. Right-click breakpoint -> Condition
  2. 2. Set condition expression

Practical Example

for (size_t i = 0; i < students.size(); ++i) {
    // Breakpoint condition: i == 2 || students[i].second > 90
    std::cout << students[i].first << ": " << students[i].second << std::endl;
}

Common Conditional Expressions

  • <span>i == 5</span>:Stop at the 5th iteration
  • <span>name == "张三"</span>:Stop when processing specific data
  • <span>score < 0 || score > 100</span>:Detect abnormal data
  • <span>ptr == nullptr</span>:Detect null pointer

Exception Breakpoints

Concept Explanation:Automatically stop debugging when an exception is thrownSetting Path:Debug -> Windows -> Exception Settings

Practical Case

#include <stdexcept>

void riskyFunction(int index) {
    std::vector<int> data = {1, 2, 3, 4, 5};
    
    if (index < 0 || index >= data.size()) {
        throw std::out_of_range("Index out of range");  // Exception breakpoint will stop here
    }
    
    std::cout << "Data: " << data[index] << std::endl;
}

int main() {
    try {
        riskyFunction(10);  // Intentionally passing an incorrect index
    } catch (const std::exception& e) {
        std::cout << "Caught Exception: " << e.what() << std::endl;
    }
    return 0;
}

Memory and Call Stack Analysis

Call Stack Window:Debug -> Windows -> Call Stack

  • • View function call chain
  • • Double-click any stack frame to jump to corresponding code
  • • View local variables at each function level

Memory Window:Debug -> Windows -> Memory

int* ptr = new int[10];
// In the memory window, input ptr to view memory content
// You can see the continuous memory distribution
delete[] ptr;

VS Code Debug Configuration

launch.json Configuration

GCC/MinGW Configuration

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "C++ Debug (GCC)",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/${fileBasenameNoExtension}.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: g++ build active file"
        }
    ]
}

MSVC Configuration

{
    "name": "C++ Debug (MSVC)",
    "type": "cppvsdbg",
    "request": "launch",
    "program": "${workspaceFolder}/build/${fileBasenameNoExtension}.exe",
    "args": [],
    "stopAtEntry": false,
    "cwd": "${workspaceFolder}",
    "environment": [],
    "console": "integratedTerminal",
    "preLaunchTask": "C/C++: cl.exe build active file"
}

Clang Configuration

{
    "name": "C++ Debug (Clang)",
    "type": "cppdbg",
    "request": "launch",
    "program": "${workspaceFolder}/build/${fileBasenameNoExtension}",
    "args": [],
    "stopAtEntry": false,
    "cwd": "${workspaceFolder}",
    "environment": [],
    "externalConsole": false,
    "MIMode": "lldb",
    "preLaunchTask": "C/C++: clang++ build active file"
}

tasks.json Build Configuration

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "g++",
            "args": [
                "-fdiagnostics-color=always",
                "-g",                    // Generate debug information
                "-std=c++17",           // C++ standard
                "-Wall",                // Enable warnings
                "-Wextra",              // Extra warnings
                "${file}",
                "-o",
                "${fileDirname}/build/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": ["$gcc"],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

VS Code Debug Interface Overview

Debug Sidebar Features

1. Variables Area

  • Local Variables:All variables in the current scope
  • Watch:Manually added expressions
  • Call Stack:Function call chain

2. Breakpoint Management

  • • View all breakpoints
  • • Enable/Disable breakpoints
  • • Edit breakpoint conditions

3. Debug Console

// Execute expressions during debugging
students.size()
students[0].first
total

Inline Debug Information

Variable Value Display

int total = 0;                    // total: 0
for (const auto& student : students) {   // student: {"张三", 85}
    total += student.second;      // total: 85 -> 177 -> 255
}
double average = total / students.size(); // average: 85.0

Recommended VS Code Extensions

Essential Debugging Extensions

  1. 1. C/C++ Extension Pack:Official C++ support from Microsoft
  2. 2. Code Runner:Quickly run code
  3. 3. Better Comments:Beautify comments
  4. 4. Bracket Pair Colorizer 2:Bracket pairing
  5. 5. GitLens:Git enhancement

Debug Enhancement Extensions

  • Hex Editor:View binary files
  • Memory View:View memory layout
  • Disassembly View:View assembly code
  • Native Debug:Enhance native debugging capabilities

Practical Debugging Cases

Segmentation Fault Debugging Practice

Problem Code

#include <iostream>

class BuggyArray {
private:
    int* data;
    size_t size;
    
public:
    BuggyArray(size_t n) : size(n) {
        data = new int[n];
        // Bug1: Forgot to initialize array contents
    }
    
    ~BuggyArray() {
        delete[] data;
    }
    
    void setValue(size_t index, int value) {
        data[index] = value;  // Bug2: No boundary check
    }
    
    void printAll() {
        for (size_t i = 0; i <= size; ++i) {  // Bug3: Should be i < size
            std::cout << "data[" << i << "] = " << data[i] << std::endl;
        }
    }
    
    // Bug4: Forgot to implement copy constructor and assignment operator
};

int main() {
    BuggyArray arr(5);
    arr.setValue(0, 10);
    arr.setValue(10, 100);  // Bug: Index out of bounds
    arr.printAll();         // Bug: Loop boundary error
    return 0;
}

Debugging Steps

Step 1: Locate the Crash Point

  1. 1. Set a breakpoint at the first line of the <span>main</span> function
  2. 2. Step through, observing where the program crashes
  3. 3. Check the call stack to determine the crashing function

Step 2: Check Variable States

// Set a breakpoint in the setValue function
void setValue(size_t index, int value) {
    // Watch variables: index, value, size, data
    // Conditional breakpoint: index >= size
    data[index] = value;  // When index=10, size=5, the problem is discovered
}

Step 3: Analyze Memory State

  • • View the memory pointed to by <span>data</span><span> pointer in the memory window</span>
  • • Check if an unallocated memory area was accessed

Fixed Code

class SafeArray {
private:
    std::vector<int> data;  // Use vector instead of raw pointer
    
public:
    SafeArray(size_t n) : data(n, 0) {}  // Automatically initialized to 0
    
    void setValue(size_t index, int value) {
        if (index >= data.size()) {
            throw std::out_of_range("Index out of range");
        }
        data[index] = value;
    }
    
    void printAll() {
        for (size_t i = 0; i < data.size(); ++i) {  // Fixed loop condition
            std::cout << "data[" << i << "] = " << data[i] << std::endl;
        }
    }
};

Logic Error Debugging Practice

Problem Code:Quick Sort Implementation (with bug)

#include <vector>
#include <algorithm>

int partition(std::vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    
    for (int j = low; j <= high - 1; j++) {
        if (arr[j] < pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(std::vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi, high);      // Bug: Should be pi + 1
    }
}

int main() {
    std::vector<int> data = {64, 34, 25, 12, 22, 11, 90};
    quickSort(data, 0, data.size() - 1);
    
    // Output result verification
    for (int x : data) std::cout << x << " ";
    return 0;
}

Debugging Strategy

1. Set Debug Breakpoints

void quickSort(std::vector<int>& arr, int low, int high) {
    if (low < high) {              // Breakpoint 1: Check recursion condition
        int pi = partition(arr, low, high);  // Breakpoint 2: Check partition result
        
        // Watch variables: low, high, pi, arr
        quickSort(arr, low, pi - 1);    // Breakpoint 3: Left subarray
        quickSort(arr, pi, high);       // Breakpoint 4: Right subarray (bug location)
    }
}

2. Use Conditional Breakpoints

  • • Condition: <span>high - low <= 1</span>(Observe recursion boundary)
  • • Condition: <span>pi == low || pi == high</span>(Detect partition anomalies)

3. Watch Expressions

// Add in watch window
arr                           // View entire array state
high - low                    // View subarray size
arr[pi]                       // View pivot element

Fix Plan

quickSort(arr, pi + 1, high);  // Correct right subarray range

Memory Leak Debugging

Problem Code

#include <iostream>
#include <memory>

class ResourceManager {
private:
    int* data;
    
public:
    ResourceManager(size_t size) {
        data = new int[size];
        std::cout << "Allocated memory: " << data << std::endl;
    }
    
    ~ResourceManager() {
        delete data;  // Bug: Should be delete[] data
        std::cout << "Freed memory: " << data << std::endl;
    }
};

void memoryLeakExample() {
    ResourceManager* manager = new ResourceManager(50);
    // Forgot delete manager;  // Bug: Memory leak
}

int main() {
    memoryLeakExample();
    std::cout << "Program ends" << std::endl;
    return 0;
}

Debugging Tips

1. Refactor Using Smart Pointers

class SafeResourceManager {
private:
    std::unique_ptr<int[]> data;
    
public:
    SafeResourceManager(size_t size) : data(std::make_unique<int[]>(size)) {
        std::cout << "Allocated memory: " << data.get() << std::endl;
    }
    
    ~SafeResourceManager() {
        std::cout << "Automatically freed memory: " << data.get() << std::endl;
    }
};

void safeMemoryExample() {
    auto manager = std::make_unique<SafeResourceManager>(50);
    // Automatically manage memory, no need for manual delete
}

2. Use Debugging Tools for Detection

  • Visual Studio:Diagnostic Tools -> Memory Usage
  • VS Code + AddressSanitizer:Add <span>-fsanitize=address</span> at compile time
  • Valgrind(Linux):Memory error detection

Example Compile Options

# Enable memory detection
g++ -g -fsanitize=address -fsanitize=undefined main.cpp -o main

# Automatically detects memory issues at runtime
./main

Debugging Tips Summary

Best Practices for Debugging

Code Habits

// 1. Use assertions to check preconditions
#include <cassert>

void processArray(const std::vector<int>& arr) {
    assert(!arr.empty());          // Ensure array is not empty
    assert(arr.size() <= 1000);    // Ensure array size is reasonable
    // Processing logic...
}

// 2. Add meaningful error messages
double divide(double a, double b) {
    if (b == 0.0) {
        throw std::invalid_argument("Denominator cannot be zero: b = " + std::to_string(b));
    }
    return a / b;
}

// 3. Use RAII for resource management
class FileHandler {
    std::ifstream file;
public:
    FileHandler(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            throw std::runtime_error("Cannot open file: " + filename);
        }
    }
    // Automatically close file on destruction
};

Techniques to Improve Debugging Efficiency

1. Preventive Programming

  • • Use static analysis tools (e.g., PVS-Studio, Clang Static Analyzer)
  • • Enable warnings at compile time (-Wall -Wextra -Werror)
  • • Use modern C++ features (smart pointers, RAII)

2. Quick Localization Techniques

  • Binary Search Debugging:Set breakpoints in the middle of suspicious code segments
  • Log-Driven Debugging:Add log outputs at critical locations
  • Rubber Duck Debugging:Explain code logic to someone else

3. Combination of Debugging Tools

# Compile option combinations
g++ -g -O0 -Wall -Wextra -fsanitize=address -fsanitize=undefined main.cpp

# IDE + Command Line Tools
# Visual Studio: Powerful GUI debugging
# GDB/LLDB: Command line precise control
# Valgrind: Memory issue detection
# AddressSanitizer: Runtime memory checks

Debugging Strategies for Different Problem Types

Problem Type Recommended Tools Debugging Focus Key Techniques
Segmentation Fault GDB + AddressSanitizer Memory Access Stack Trace + Memory Check
Logic Error IDE Breakpoint Debugging Variable State Step Execution + Variable Watch
Performance Issue Profiler Tools Hotspot Functions Performance Counting + Time Measurement
Memory Leak Valgrind + Smart Pointers Resource Management Lifecycle Tracking
Multithreading Bug Thread Sanitizer Race Conditions Thread Synchronization + Atomic Operations

Common Problem Solutions

Debugging Environment Issues

Problem 1: Breakpoints Not Hitting

// Check List:
// 1. Is it compiled in Debug mode?
// 2. Is debug information generated (-g option)?
// 3. Is optimization enabled (which can cause breakpoints to shift)?
// 4. Is the code actually executed to the breakpoint location?

// Solution
#ifdef _DEBUG
    std::cout << "Running in debug mode" << std::endl;
#endif

Problem 2: Variable Displayed as “Optimized Out”

# Reason: Compiler optimization causes variables to be eliminated
# Solution: Use -O0 to disable optimization

# CMakeLists.txt Configuration
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")

Problem 3: Chinese Characters Displaying as Garbled

// Set console encoding on Windows
#ifdef _WIN32
#include <windows.h>
int main() {
    SetConsoleOutputCP(CP_UTF8);
    std::cout << "Chinese Test" << std::endl;
    return 0;
}
#endif

// Or use wide characters
std::wcout << L"Chinese Test" << std::endl;

Cross-Platform Debugging Differences

Windows (Visual Studio)

  • • Powerful visual interface
  • • Built-in performance profiler
  • • Excellent IntelliSense features
  • • Rich debugging windows

Linux (GDB + VS Code)

  • • Command line debugging with precise control
  • • Valgrind memory detection
  • • System-level debugging support
  • • Complete open-source toolchain

macOS (Xcode + LLDB)

  • • LLDB modern debugger
  • • Excellent memory graph visualization
  • • Metal performance analysis
  • • System framework debugging support

Advanced Debugging Techniques

Remote Debugging

VS Code Remote Debugging Configuration

{
    "name": "Remote Debug",
    "type": "cppdbg",
    "request": "attach",
    "program": "/remote/path/to/executable",
    "processId": "${command:pickRemoteProcess}",
    "MIMode": "gdb",
    "miDebuggerServerAddress": "localhost:2345"
}

Multithreading Debugging

Key Points for Thread Debugging

#include <thread>
#include <mutex>

std::mutex mtx;
int shared_data = 0;

void worker_thread(int id) {
    // Set breakpoints to observe thread execution
    std::lock_guard<std::mutex> lock(mtx);
    shared_data += id;
    std::cout << "Thread " << id << " updated data to " << shared_data << std::endl;
}

int main() {
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(worker_thread, i);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

Debugging Tips

  • • Use the thread window to view all thread states
  • • Set thread-specific breakpoints
  • • Observe lock contention and deadlock situations

Conclusion

The IDE debugger is one of the most important tools for C++ developers. Through this article, you should be able to:

🚀 Key Points for Efficiency Improvement

  1. 1. Say Goodbye to Printf Debugging:Replace inefficient print debugging with IDE debuggers
  2. 2. Preventive Programming:Use modern C++ features to reduce bug occurrences
  3. 3. Combination of Tools:IDE + Static Analysis + Dynamic Detection
  4. 4. Continuous Learning:Keep up with new features and best practices in debugging tools

💡 Suggestions for Continuous Improvement

  • • Practice using debugging features of different IDEs
  • • Learn to use command line debugging tools (GDB, LLDB)
  • • Stay updated on new features and best practices in debugging tools
  • • Cultivate good coding habits to reduce debugging needs
  • • Learn advanced debugging techniques: performance analysis, multithreading debugging, and remote debugging methods

Tag Classification:#IDE Debugging #Visual Studio #VS Code #Breakpoint Debugging #Variable Watch #C++ Debugging #Development Tools #Program Debugging

After mastering basic debugging techniques, you can further learn about performance analysis tools, multithreading debugging techniques, and remote debugging methods in production environments.

Feel free to follow usC++ Notes: Debugging Techniques for Visual Studio and VS Code - A Complete Guide to IDE DebuggersGive us a like if you enjoy itC++ Notes: Debugging Techniques for Visual Studio and VS Code - A Complete Guide to IDE DebuggersClick to see more of the best contentC++ Notes: Debugging Techniques for Visual Studio and VS Code - A Complete Guide to IDE Debuggers

Leave a Comment