Ryan Schachte's Blog
C++ development setup on Mac & VSCode (debugging, compilation, intellisense) May 4th, 2024

It’s honestly surprising how frustrating it is to setup a clean C++ development environment on Mac. VSCode is the standard tool “IDE” of choice these days and C++ is no exception to the list of languages that is used within the VSCode editor.

There have been various Mac OS updates over the years that break things such as Catalina swapping C++ header and library directories from /usr/include and /usr/lib into XCode stuff. Let’s fix things up and get a debugger, Intellisense and compilation working on Mac.

Note: Not every step might be required for you, but there is a lot of cross-device issues with C++ configuration on Mac depending on the architecture and OS version, so the idea is to include as many debug options as possible. For reference, I’m using an M1 Mac (ARM).

Starting fresh with VSCode (optional)

I start with a clean slate if I’m having issues. Be sure to perform necessary backups:

  • rm -rf ~/.vscode
  • rm -rf $HOME/Library/Application Support/Code

Also, drag Visual Studio Code from your /Applications dir to the trash.

Reinstall using Homebrew or download from Microsoft here. Personally, I download from Microsoft directly.

brew install --cask visual-studio-code

Or if you already have it then:

brew reinstall visual-studio-code

Open it by running code in your terminal to ensure it was installed properly.

Compiler

clang is the c++ compiler Apple provides via the XCode tools. You can check if you have it via:

clang --version

If you have errors, then just run:

xcode-select --install

To prevent any issues with missing libraries, you can install XCode from Apple from the App Store if you don’t have it.

With Homebrew, you can get clang & clang++ (For C and C++ respectively) installed via LLVM.

brew update
brew install llvm

Verify path is correct for both:

 which clang
/opt/homebrew/opt/llvm/bin/clang
 
 which clang++
/opt/homebrew/opt/llvm/bin/clang++

Mac will install C++ header files and libraries in some random directory. You can find the directory via xcrun by running:

xcrun --show-sdk-path

This path is important because we need to tell the compiler to look at this root via the CPLUS_INCLUDE_PATH environment variable. Additionally, we should include the LLVM path as a higher precedent search location.

export CPLUS_INCLUDE_PATH=/opt/homebrew/opt/llvm/include/c++/v1:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include

I’ve added this into my ~/.zshrc file to export the PATH by default for all future terminal sessions.

If you run into any linker errors, then tell the linker to look for libraries in the following path via the LIBRARY_PATH env variable.

export LIBRARY_PATH=$LIBRARY_PATH:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib

Again, I export this in ~/.zshrc.

Extensions

VSCode is not a traditional C/C++ IDE, but Microsoft has extensions that keep you thinking it is. Install the following extensions:


Name: C/C++
Id: ms-vscode.cpptools
Description: C/C++ IntelliSense, debugging, and code browsing.
Version: 1.19.9
Publisher: Microsoft
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools

If you create a new folder with a new C++ file in it like:

mkdir cpp_fun
touch main.cpp

There are two approaches you can use for configuration. Default and per-project. I’ll explain both.

Default Configuration

Verify the paths match your system

Hit <cmd> + <shift> + P you should see: Preferences: Open User Settings (JSON)

{
    ...
  "C_Cpp.default.defines": [],
  "C_Cpp.default.macFrameworkPath": [],
  "C_Cpp.default.compilerPath": "/opt/homebrew/opt/llvm/bin/clang",
  "C_Cpp.default.cStandard": "c23",
  "C_Cpp.default.cppStandard": "c++23",
  "C_Cpp.default.intelliSenseMode": "macos-clang-arm64",
  "C_Cpp.intelliSenseEngineFallback": "enabled",
  "C_Cpp.default.includePath": [
    "${workspaceFolder}/**",
    "/opt/homebrew/opt/llvm/include/c++/v1",
    "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include"
  ]
}

You can trigger a new c_cpp_properties.json file by cmd + shift + p and selecting Edit Configurations. It should auto gen the file with the following:

{
    "configurations": [
        {
            "name": "Mac",
            "includePath": [
                "${default}"
            ]
        }
    ],
    "version": 4
}

And inherit the defaults we defined above.

Per Project Configuration

Hit <cmd> + <shift> + P you should see:

C/C++ Edit Configurations (JSON)

This will auto generate a c_cpp_properties.json file underneath .vscode at the root.

I have it configured with the following:

{
    "configurations": [
        {
            "name": "Mac",
            "includePath": [
                "${workspaceFolder}/**",
                "/opt/homebrew/opt/llvm/include/c++/v1",
                "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include"
            ],
            "defines": [],
            "macFrameworkPath": [],
            "compilerPath": "/opt/homebrew/opt/llvm/bin/clang",
            "cStandard": "c23",
            "cppStandard": "c++23",
            "intelliSenseMode": "macos-clang-arm64"
        }
    ],
    "version": 4
}

I explicitly removed all macFrameworkPath values as it made my Intellisense ugly.

Debugging

For debugging, I’m using CodeLLDB as shown above. If you select the Debug button in the sidebar, it’ll ask you to create a new configuration. Select the second option:

The configuration in .vscode/tasks.json might look like:

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: clang++ build active file",
            "command": "/opt/homebrew/opt/llvm/bin/clang++",
            "args": [
                "-fcolor-diagnostics",
                "-fansi-escape-codes",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

Active debugger

Example stepping through the program

Source code

Very complicate program code example /s.

#include <iostream>
#include <string>
 
int main()
{
    std::string greeting = "hello";
    std::cout << greeting << std::endl;
}

Yay 👏, we did it. Clearly, this is a lot more fun to configure than more modern day languages like Go and Javascript! If you have updates or suggestions, post them down below.

Note: If you have any issues with the extension, double check paths. Double

Care to comment?