Build and Tooling
|
This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com. This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Compilers and Flags
Covered in Getting Started; flags worth knowing beyond
-std=c++23 -Wall -Wextra:
| Flag | Effect |
|---|---|
|
Treat every warning as an error — add once a codebase is warning-clean. |
|
Optimization level: none (fast builds, best debugging) through aggressive. |
|
Include debug symbols (needed by debuggers and most sanitizers). |
|
Enable ASan/UBSan (see Performance). |
|
Warn when a variable shadows an outer-scope one — not in |
|
Warn on implicit narrowing conversions — catches the pitfalls from Basic Types and Values. |
CMake
The de facto standard C++ build-system generator — CMake itself generates Makefiles/Ninja files/Visual Studio projects rather than compiling directly:
cmake_minimum_required(VERSION 3.28)
project(MyApp CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(mylib src/mylib.cpp)
target_include_directories(mylib PUBLIC include)
add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# Sanitizers behind an option -- the Continuous Integration section below turns this on
# for the fast per-commit job.
option(ENABLE_SANITIZERS "Build with ASan and UBSan" OFF)
if(ENABLE_SANITIZERS)
target_compile_options(myapp PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
target_link_options(myapp PRIVATE -fsanitize=address,undefined)
endif()
enable_testing()
add_subdirectory(tests)
Modern CMake is target-based: target_link_libraries/target_include_directories attach properties to a
specific target (propagating to whatever links against it, if PUBLIC), rather than the old global
include_directories()/link_libraries() that leaked into every target in the project.
CMakePresets.json (CMake 3.19+) captures reproducible configure/build/test invocations (compiler, generator,
flags, cache variables) in one checked-in file, instead of everyone remembering their own cmake -B build
-DCMAKE_BUILD_TYPE=… invocation:
{
"version": 6,
"configurePresets": [
{
"name": "debug-asan",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug-asan",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS": "-fsanitize=address,undefined -g"
}
}
]
}
cmake --preset debug-asan
cmake --build --preset debug-asan
Package Managers: vcpkg and Conan
Both resolve and build third-party C++ dependencies, integrating with CMake via a toolchain file (vcpkg) or a
generated find_package-compatible config (Conan):
# vcpkg
vcpkg install fmt catch2
cmake -B build -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
# Conan
conan install . --output-folder=build --build=missing
cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
Before either, C++ had no de facto package manager — most projects either vendored dependencies directly or
relied on system package managers (apt, brew), which vcpkg/Conan largely superseded for portable,
per-project dependency management.
clang-format
Automated, configurable code formatting — removes formatting bikeshedding from code review entirely:
# .clang-format
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
clang-format -i src/*.cpp include/*.h # reformat in place
clang-tidy
Static analysis covering far more than a compiler’s own warnings — modernization suggestions
(use-nullptr, use-auto), bug-prone-pattern detection, and Core Guidelines checks:
clang-tidy src/main.cpp -- -std=c++23
clang-tidy --checks='modernize-*,bugprone-*' src/main.cpp -- -std=c++23
compile_commands.json
A per-file record of the exact compiler invocation used to build it — generated automatically by CMake with
CMAKE_EXPORT_COMPILE_COMMANDS=ON (or by Ninja/Bear for other build systems), and consumed by clang-tidy,
clangd (editor tooling), and other analysis tools so they see the actual flags/include paths a file was built
with, rather than guessing:
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# generates build/compile_commands.json
Compiler Explorer
Covered in Getting Started — Compiler Explorer remains the fastest way to check what a specific compiler/flag/standard combination actually does with a snippet, without setting up a local project.
Valgrind
Installing Valgrind
# Debian / Ubuntu
$ sudo apt-get install valgrind
# Fedora / RHEL
$ sudo dnf install valgrind
# macOS (Homebrew) -- the mainline formula lags behind the newest macOS/Xcode releases;
# on a recent macOS, use the community tap instead:
$ brew install valgrind # older macOS
$ brew tap LouisBrunner/valgrind && brew install --HEAD LouisBrunner/valgrind/valgrind
# Windows -- no native build; run it inside WSL (Ubuntu), same commands as above.
|
Valgrind supports Linux, FreeBSD, Solaris and Android natively; macOS support trails new OS releases; there is
no native Windows build, so a Windows-only team needs WSL or a Linux CI runner — this is why the CI example
below targets |
Running Memcheck on C++ Binaries
Valgrind instruments compiled machine code, so it works transparently on C++ binaries with no special flags:
it catches raw new/delete mistakes exactly like C’s malloc/free, and it correctly follows RAII
destructor calls and exception unwinding.
$ valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./app
Memcheck’s leak report groups every unfreed block into one of four categories:
-
definitely lost — a real leak, with no pointer chain to the block.
-
indirectly lost — lost because the block(s) pointing to it are themselves lost.
-
possibly lost — only an interior-pointer chain reaches it; ambiguous.
-
still reachable — a pointer to the block still exists at exit; often not a bug.
See C: Build and Tooling for the full suppressions/ exit-code/XML-output reference, which applies identically here.
Continuous Integration
The fast per-commit job builds in Debug with the ENABLE_SANITIZERS option already defined in the CMake
example above, and runs the test suite through CTest:
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_SANITIZERS=ON
$ cmake --build build -j
$ ctest --test-dir build --output-on-failure
Valgrind is roughly 20-30x slower than an uninstrumented run, so it belongs in a separate, scheduled job rather than blocking every commit alongside the sanitizer-based checks above:
name: memcheck
on:
push:
branches: [ main ]
pull_request:
schedule:
- cron: '0 3 * * *' # nightly -- Valgrind is far slower than the sanitizer job above
jobs:
valgrind:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Valgrind
run: sudo apt-get update && sudo apt-get install -y valgrind
- name: Configure and build (Debug, no sanitizers -- Valgrind instruments the plain binary)
run: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j
- name: Run tests under Memcheck
run: |
ctest --test-dir build --output-on-failure \
-T memcheck --overwrite MemoryCheckCommand=/usr/bin/valgrind \
--overwrite "MemoryCheckCommandOptions=--leak-check=full --show-leak-kinds=all --error-exitcode=1"
ctest -T memcheck is CTest’s built-in Valgrind integration, configured via MemoryCheckCommand: it runs every
registered test under Memcheck and fails the step on --error-exitcode, without needing to invoke valgrind on
each test binary by hand.
See Also
-
C: Build and Tooling — the same compilers, sanitizers and CMake, without the C++-oriented package-manager ecosystem, plus the same Valgrind installation steps and CI setup.