VS Code C++ Setup: IntelliSense, Build Tasks, and Debugging
[C++ Hands-On Guide #3] VS Code C++ Configuration
VS Code is not a C++ IDE out of the box. It is an editor that, with Microsoft’s C/C++ extension, talks to three separate things you install yourself: a compiler (g++, clang++ or MSVC), a debugger (GDB, LLDB or the Visual Studio debugger), and optionally a build system such as CMake. Each connection has its own configuration file in .vscode/, and most setup problems come from those files disagreeing with each other or with the command line.
“The terminal builds, but the editor is full of red squiggles”
The C++ extension needs to know which compiler you use and where system headers live. If compilerPath is wrong, <iostream> / <vector> may show errors even when g++ succeeds.
IntelliSense = completion, signature help, and error squiggles while typing.
This is the single most common confusion, and it happens because IntelliSense is not your compiler. The extension runs its own parser (derived from the EDG front end) and only borrows information from the compiler at compilerPath: it queries it for its built-in include directories and predefined macros. A successful terminal build therefore proves nothing about IntelliSense, and a clean editor proves nothing about the build. When the two disagree, fix the configuration, not the code.
Scenario quick hits
- New teammate, new PC: Commit
.vscode/with OS-specificc_cpp_properties.jsonvariants or documentcompilerPathper OS. - CMake project, IntelliSense broken: Generate
compile_commands.json(CMAKE_EXPORT_COMPILE_COMMANDS) or configure CMake Tools. - WSL builds: Open the folder in WSL (Remote - WSL); point
compilerPathto/usr/bin/g++. preLaunchTasknot found:tasks.jsonlabelmust exactly matchlaunch.jsonpreLaunchTask.- macOS LLDB errors: Use
MIMode: lldb; install Xcode CLI tools. Prerequisites: A working compiler (#2) and optionally CMake (#4).
flowchart TB
subgraph editor[VS Code]
A[Edit sources]
B[IntelliSense]
end
subgraph config[.vscode/]
C[c_cpp_properties.json]
D[tasks.json]
E[launch.json]
end
subgraph run[Build & run]
F[g++/clang++]
G[Binary]
H[GDB/LLDB]
end
C --> B
D --> F
F --> G
E --> H
H --> G
Install VS Code
Download from code.visualstudio.com. Enable Add to PATH so code . works from a terminal.
Install the C/C++ extension (Microsoft). Optional: CMake Tools, C/C++ Extension Pack.
IntelliSense — c_cpp_properties.json
Create .vscode/c_cpp_properties.json:
{
"configurations": [
{
"name": "Linux",
"includePath": ["${workspaceFolder}/**"],
"defines": [],
"compilerPath": "/usr/bin/g++",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
Windows (MinGW) example:
"compilerPath": "C:/msys64/mingw64/bin/g++.exe",
"intelliSenseMode": "windows-gcc-x64"
macOS (Clang):
"compilerPath": "/usr/bin/clang++",
"intelliSenseMode": "macos-clang-x64"
Run which g++ / where g++ and paste the exact path into compilerPath.
What each field actually controls:
compilerPathis the most important line. The extension runs this compiler once to discover system include paths and built-in macros such as__GNUC__or_WIN32. You do not need to list/usr/include/c++/13inincludePath; if you find yourself doing that,compilerPathis wrong.includePathis only for your headers and third-party libraries.${workspaceFolder}/**makes the extension search every subfolder recursively, which is convenient for small projects and slow on big ones (it also picks up headers from build directories and vendored copies, so two same-named headers can resolve to the wrong one).definesshould mirror the-Dflags from your build. If the build uses-DUSE_SSLand IntelliSense does not,#ifdef USE_SSLblocks appear greyed out and completion inside them stops working.cppStandardmust match-std=in the build task, or IntelliSense flags valid C++20 code as errors (or accepts code the compiler rejects).intelliSenseModetells the parser which compiler’s quirks and type sizes to emulate. Amsvc-x64mode with a MinGWg++.exeis a classic mismatch that produces odd errors aroundlongsizes and compiler extensions.
The quickest check is the command palette entry C/C++: Log Diagnostics, which prints the include paths and defines IntelliSense is really using for the current file. When squiggles do not make sense, I read that output before touching anything else; it usually shows either the wrong compiler or a missing define within seconds.
Build tasks — tasks.json
Default build for the current file:
{
"version": "2.0.0",
"tasks": [
{
"label": "C++ Build",
"type": "shell",
"command": "g++",
"args": [
"-g",
"-std=c++17",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"group": { "kind": "build", "isDefault": true }
}
]
}
Ctrl+Shift+B (mac: Cmd+Shift+B) runs the default build task. For multiple files, list them explicitly or switch to CMake.
${file} is the file in the active editor tab, so this task compiles only that file. That is the right shape for single-file exercises and wrong for anything with more than one .cpp: pressing Ctrl+Shift+B while utils.cpp is open tries to build utils.cpp alone and fails with undefined reference to 'main'. Two additions make the task much more useful. Add -Wall -Wextra to the arguments, because VS Code shows compiler warnings only if the compiler emits them. And add "problemMatcher": ["$gcc"], which parses the compiler output so errors appear in the Problems panel with clickable locations instead of only as text in the terminal.
"type": "shell" runs the command through your shell, so shell quoting rules apply to the arguments; "type": "process" runs the executable directly and avoids quoting surprises with paths that contain spaces.
Debugging — launch.json
Linux example (GDB):
{
"version": "0.2.0",
"configurations": [
{
"name": "C++ Debug",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"cwd": "${fileDirname}",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"preLaunchTask": "C++ Build"
}
]
}
macOS: "MIMode": "lldb" (often omit miDebuggerPath).
Windows MinGW: point miDebuggerPath to gdb.exe.
F5 starts debugging; ensure preLaunchTask matches tasks.json label.
"type": "cppdbg" means the extension drives GDB or LLDB through the machine interface (MI), which is why MIMode and miDebuggerPath exist. On Windows with MSVC you use "type": "cppvsdbg" instead, which talks to the Visual Studio debugger engine and ignores the MI settings. program must point at the binary the build task produces; if the two paths drift apart, F5 happily launches an old binary and breakpoints stop matching the source.
The -g flag in the build task is what makes debugging possible at all: without it the binary has no line tables and breakpoints show as hollow circles (“unverified”). Keep optimization at -O0 (the default without -O) while stepping; at -O2 the compiler reorders and removes code, so execution jumps around and many variables show <optimized out>. Two other settings are worth knowing: "externalConsole": true gives programs that read from std::cin a real console window, and "stopAtEntry": true pauses at main so you can set breakpoints before anything runs.
On macOS, cppdbg relies on an lldb-mi bridge, and that bridge has historically been the fragile part of the setup (launch failures after Xcode updates, early lack of native Apple Silicon support). If it gives you trouble, the CodeLLDB extension ("type": "lldb") talks to LLDB directly and is a common alternative.
Common issues
| Symptom | Fix |
|---|---|
| Red squiggles on standard headers | Fix compilerPath + intelliSenseMode |
| Header not found | Add paths to includePath |
preLaunchTask not found | Match strings exactly |
| C++20 features flagged | Set cppStandard to c++20 and add -std=c++20 to tasks |
undefined reference in tasks build | Add all .cpp files or use CMake |
| Variables “optimized out” | Build with -O0 -g for debugging |
Reset IntelliSense: Command Palette → C/C++: Reset IntelliSense Database.
Most rows in this table are the same underlying problem seen from different angles: one of the three files disagrees with the actual build. undefined reference is a linker error from the build task, not an IntelliSense problem, so no change to c_cpp_properties.json will fix it. Conversely, red squiggles with a successful build are never fixed by changing tasks.json. Deciding first which of the two tools is complaining saves a lot of random edits.
On Windows, a frequent variant is having more than one toolchain installed (an old MinGW, MSYS2’s UCRT64 and MinGW64 environments, and MSVC). where g++ may list several, the terminal uses the first one on PATH, and compilerPath points at another. The program then builds with one standard library and IntelliSense parses against a different one; keep a single toolchain on PATH or use absolute paths in both files.
Worked example (multi-file)
main.cpp, utils.h, utils.cpp — use a second task:
{
"label": "C++ Build Multi-File",
"type": "shell",
"command": "g++",
"args": ["-g", "-std=c++17", "main.cpp", "utils.cpp", "-o", "myapp"],
"options": { "cwd": "${workspaceFolder}" },
"group": "build"
}
Point launch.json program to ${workspaceFolder}/myapp and preLaunchTask to that label.
CMake: Install CMake Tools → Configure → Build (F7) → debug with the generated targets.
Listing files by hand works up to a handful of sources, but it recompiles every file on every build and you must remember to add new files in two places. That is the point where I stop maintaining tasks.json and switch to CMake: CMake Tools builds only what changed, launches the selected target under the debugger without a launch.json (via CMake: Debug), and exports compile_commands.json so IntelliSense sees exactly the flags each file is compiled with. Once that file exists, set "configurationProvider": "ms-vscode.cmake-tools" in c_cpp_properties.json (or in settings), and the includePath/defines lists stop mattering.
Productivity
- Ctrl+Shift+B — build
- F5 — debug
- Ctrl+P — quick open
- Ctrl+Shift+P — command palette
Production patterns
- Commit .vscode/ for shared team settings (exclude only personal noise if needed).
- Align
tasks.jsoncommands with CI (g++ ...lines) to avoid “works locally, fails in CI”. - For large projects, prefer compile_commands.json from CMake instead of hand-maintained
includePath.
Committing .vscode/ is a trade-off. tasks.json and launch.json are usually portable if they use variables like ${workspaceFolder}, but c_cpp_properties.json with an absolute compilerPath works on one machine only. A common arrangement is to commit tasks and launch configurations, keep compiler paths out of shared files by relying on compile_commands.json, and let each developer’s user settings supply the rest.
Checklist
- C/C++ extension installed
-
compilerPathmatcheswhich g++/clang++ -
cppStandardmatches project - Build task works (Ctrl+Shift+B)
- Debug works (F5) with matching
preLaunchTask
Next: CMake intro (#4)
Previous: Compiler basics (#2)