Contents
  1. The Two Files and What Each Does
  2. tasks.json: Compiling with g++
  3. launch.json: Starting the Debugger
  4. The Label Must Match Exactly
  5. Debug Symbols and the -g Flag
  6. A Complete Reference Setup
  7. What You Can Do Now
← All posts

Debugging C++ in VSCode: tasks.json, launch.json, and the Build Step

Debugging C++ in VSCode requires two configuration files working together: tasks.json compiles the code, launch.json launches the debugger. Understanding how they connect prevents the most common setup failures.

Debugging C++ in VSCode is not a single step. Unlike Python, where the debugger can launch your script directly, C++ must be compiled first. VSCode separates these two concerns into two configuration files: tasks.json defines how to build the code, and launch.json defines how to launch the debugger. The link between them is a single field called preLaunchTask. When that link is misconfigured, the debugger either launches stale code or fails to start entirely.

The Two Files and What Each Does

.vscode/tasks.json defines shell commands that VSCode can run as tasks. For C++, this is where you write the g++ or clang++ compilation command. VSCode runs this before launching the debugger whenever preLaunchTask references it by label.

.vscode/launch.json defines how the debugger starts: which binary to run, which debugger backend to use, where to set the working directory, and which build task to run first.

The two files are independent. launch.json does not know what is in tasks.json unless you explicitly connect them through the preLaunchTask field.

tasks.json: Compiling with g++

The task definition tells VSCode to run a shell command. For C++, that command is the compiler invocation.

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "g++-build-task-1",
            "type": "shell",
            "command": "bash",
            "args": [
                "-c",
                "g++ -std=c++11 -g *.cpp -o test"
            ],
            "problemMatcher": ["$gcc"],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Build all .cpp files in the current directory using g++"
        }
    ]
}

label is the name of the task. This exact string must match the preLaunchTask value in launch.json. A mismatch here is the most common reason the build step is skipped silently.

command and args together form the shell command. Using bash -c allows the full shell command string to be written as a single argument, which makes glob patterns like *.cpp work correctly across platforms.

The compiler flags matter. -std=c++11 sets the C++ standard. -g includes debug symbols in the compiled binary, without which the debugger cannot map machine instructions back to source lines and breakpoints will not work. -o test names the output binary test, which must match the program field in launch.json.

problemMatcher: ["$gcc"] tells VSCode how to parse compiler error output and surface it in the Problems panel. Without it, compilation errors appear only in the terminal, not as inline annotations.

launch.json: Starting the Debugger

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug active C++ file with g++",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/test",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "externalConsole": false,
            "MIMode": "lldb",
            "preLaunchTask": "g++-build-task-1"
        }
    ]
}

type: "cppdbg" uses the C/C++ extension’s debugger. This requires the Microsoft C/C++ extension to be installed in VSCode.

program is the path to the compiled binary. ${fileDirname} resolves to the directory of the file currently open in the editor. Combined with /test, this points to the binary that tasks.json produces. If the tasks.json output name and the program path do not match, the debugger will either launch the wrong binary or fail with a file not found error.

MIMode: "lldb" uses LLDB as the debug backend, which is the correct choice on macOS. On Linux, use "gdb" instead.

preLaunchTask: "g++-build-task-1" is the connection between the two files. Before the debugger starts, VSCode looks for a task in tasks.json with a label that matches this string exactly, runs it, and only proceeds to launch the debugger if the task succeeds. If the task label does not match, VSCode skips the build step and launches whatever binary already exists, which may be outdated or missing entirely.

cwd: "${fileDirname}" sets the working directory for the running program. Any relative file paths your C++ code uses will resolve from this directory.

The Label Must Match Exactly

The single most common failure is a mismatch between preLaunchTask in launch.json and label in tasks.json. The match is case-sensitive and whitespace-sensitive.

// launch.json
"preLaunchTask": "g++-build-task-1"

// tasks.json — this matches
"label": "g++-build-task-1"

// tasks.json — this does NOT match
"label": "G++-Build-Task-1"
"label": "g++ build task 1"
"label": "g++-build-task"

If VSCode cannot find a matching task label it will prompt you to select a task or report that the task was not found. The build step will not run and the debugger will launch stale or missing code.

Debug Symbols and the -g Flag

The -g compiler flag is what makes debugging possible. Without it, the compiled binary contains no information about source file names, line numbers, or variable names. The debugger cannot set breakpoints at specific lines, cannot display variable values, and cannot show the call stack in terms of your source code.

Always compile with -g when building for debugging. Remove it or replace it with -O2 when building for release, since debug symbols increase binary size and disable some compiler optimisations.

# Debug build — includes symbols, no optimisation
g++ -std=c++11 -g *.cpp -o test

# Release build — optimised, no symbols
g++ -std=c++11 -O2 *.cpp -o myapp

A Complete Reference Setup

Project structure:

my-project/
├── main.cpp
├── utils.cpp
├── utils.h
└── .vscode/
    ├── tasks.json
    └── launch.json

.vscode/tasks.json:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "g++-build-task-1",
            "type": "shell",
            "command": "bash",
            "args": ["-c", "g++ -std=c++11 -g *.cpp -o test"],
            "problemMatcher": ["$gcc"],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

.vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug C++",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/test",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "externalConsole": false,
            "MIMode": "lldb",
            "preLaunchTask": "g++-build-task-1"
        }
    ]
}
FieldFilePurpose
labeltasks.jsonName of the build task
command + argstasks.jsonThe compiler command to run
-g flagtasks.jsonIncludes debug symbols
problemMatchertasks.jsonParses compiler errors for the Problems panel
preLaunchTasklaunch.jsonMust match label exactly
programlaunch.jsonPath to the compiled binary
MIModelaunch.jsonlldb on macOS, gdb on Linux
cwdlaunch.jsonWorking directory for the running program

What You Can Do Now

Create a minimal C++ project and set up both files:

// main.cpp
#include <iostream>

int main() {
    int x = 42;
    std::cout << "x = " << x << std::endl;
    return 0;
}

Add the tasks.json and launch.json from above. Open main.cpp in VSCode, set a breakpoint on the std::cout line by clicking the gutter, then press F5. VSCode will compile the file first, then launch the debugger and pause at your breakpoint. Inspect x in the Variables panel on the left.

If the debugger launches but does not stop at your breakpoint, confirm that -g is in the compiler flags. If the build step is skipped entirely, confirm that preLaunchTask matches label character for character.

← All posts