C++ Programming HOWTO: g++, clang++, CMake and debugging on Linux
On Linux you compile C++ with g++ (GCC) or clang++ (LLVM), choose the language version with -std=c++20 or -std=c++23, and describe larger projects with CMake. Turn on warnings, build with -g, and run your tests under AddressSanitizer: those three habits catch most bugs before users do. This page sets all of that up.
The classic version of this HOWTO has been withdrawn from tldp.org for review, so this page is written from scratch for current compilers. Much of what older C++ guides built by hand is now in the standard library: std::string, containers, smart pointers and, since C++20 and C++23, ranges, std::format and std::print.
Install a compiler and tools
# Debian, Ubuntu
sudo apt install build-essential clang cmake ninja-build gdb
# Fedora, RHEL/Alma/Rocky
sudo dnf install gcc-c++ clang cmake ninja-build gdb
# Arch
sudo pacman -S base-devel clang cmake ninja gdb
Check what you got with g++ --version and clang++ --version. As of November 2026 the newest GCC release is 16.2. Distributions ship their own supported versions: Debian 13 has GCC 14; Ubuntu 24.04 LTS has GCC 13 by default with g++-14 available as a separate package; Ubuntu 26.04 LTS has GCC 15; Fedora 44 and 45 have GCC 16.
Language versions and flags
| Standard | GCC flag | Clang flag | Status (as of November 2026) |
|---|---|---|---|
| C++17 | -std=c++17 | -std=c++17 | Complete; Clang's default (as gnu++17) and GCC's default in versions 11 to 15 |
| C++20 | -std=c++20 | -std=c++20 | GCC's default (as gnu++20) since GCC 16; modules still experimental in GCC |
| C++23 | -std=c++23 | -std=c++23 | Usable; GCC labels its support experimental, Clang lists some features as missing |
| C++26 | -std=c++26 | -std=c++2c (recent Clang also accepts -std=c++26) | Experimental |
The gnu++20 and gnu++23 forms add GNU extensions; the plain c++ forms are stricter and more portable. Always state the standard explicitly, because the default changes between compiler versions. Check the one in effect with echo | g++ -dM -E -x c++ - | grep __cplusplus: 201703L means C++17, 202002L means C++20.
Compile a first program
// src/main.cpp
#include <algorithm>
#include <print>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words{"kernel", "shell", "init", "daemon"};
std::ranges::sort(words);
for (const auto &w : words)
std::println("{:8} {} letters", w, w.size());
}
g++ -std=c++23 -Wall -Wextra -Wpedantic -O2 -g src/main.cpp -o wordlist
./wordlist
daemon 6 letters
init 4 letters
kernel 6 letters
shell 5 letters
std::println comes from the C++23 header <print>, which GCC's library added in GCC 14. With -std=c++20 or GCC 13 you get "'println' is not a member of 'std'"; on Ubuntu 24.04 install g++-14 and use g++-14, or print with std::cout << std::format(…). clang++ takes the same flags and, on most distributions, uses the same GCC library (libstdc++).
Useful flags beyond the warnings:
-g: debug information for gdb. Keep it in release builds too if you can; it does not slow the program.-O2: the usual optimization level. Use-O0or-Ogwhile stepping through code.-D_GLIBCXX_ASSERTIONS: cheap bounds checks in libstdc++ containers, such asoperator[]on a vector.-fhardened(GCC 14 and later): turns on a set of hardening options in one go. See the Secure Programs HOWTO.
A CMake project
For anything beyond one file, use CMake. It generates the build files (Makefiles or Ninja) and works with every IDE and editor. Put this CMakeLists.txt next to the src directory:
cmake_minimum_required(VERSION 3.25)
project(wordlist VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_executable(wordlist src/main.cpp)
target_compile_options(wordlist PRIVATE -Wall -Wextra -Wpedantic)
option(ENABLE_SANITIZERS "Build with ASan and UBSan" OFF)
if(ENABLE_SANITIZERS)
target_compile_options(wordlist PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
target_link_options(wordlist PRIVATE -fsanitize=address,undefined)
endif()
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j
./build/wordlist
CXX_STANDARD_REQUIRED ON makes CMake fail instead of quietly using an older standard, and CXX_EXTENSIONS OFF gives -std=c++23 instead of -std=gnu++23. CMAKE_EXPORT_COMPILE_COMMANDS writes build/compile_commands.json, which clangd and other editor tools read to understand your flags. To use Clang, start with a fresh build directory and set CXX=clang++ before the first cmake call. Leave out -G Ninja to use Makefiles.
The build directory is separate from the source, so rm -rf build starts over cleanly. Use -DCMAKE_BUILD_TYPE=Release (adds -O3 -DNDEBUG) or RelWithDebInfo (-O2 -g -DNDEBUG) for releases. NDEBUG switches off assert().
Old projects and CMake 4. CMake 4.0 removed compatibility with versions older than 3.5: a project that says cmake_minimum_required(VERSION 2.8) now stops with an error. Raise the number in the project, after checking that it still builds. Debian 13 ships CMake 3.31; the current CMake documentation is for the 4.x series.
Debugging with gdb
Build with -g -O0 and run the program inside gdb. When it crashes (the session below is from an example program whose function sum follows a bad pointer), bt (backtrace) shows the chain of calls:
g++ -std=c++23 -g -O0 crash.cpp -o crash
gdb -q ./crash
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x000055555555514e in sum (n=0x10) at crash.cpp:9
9 total += n->value;
(gdb) bt
#0 0x000055555555514e in sum (n=0x10) at crash.cpp:9
#1 0x00005555555551cc in main () at crash.cpp:20
(gdb) print total
$1 = 6
(gdb) print *n
Cannot access memory at address 0x10
Other commands you will use daily: break file.cpp:42, next (over calls), step (into calls), finish, info locals, and frame 1 to look at the caller. gdb prints standard containers readably (print words shows the elements) thanks to the libstdc++ pretty printers. If a program crashed outside gdb, coredumpctl debug opens the last core dump in gdb on systemd systems.
Sanitizers
Sanitizers add run-time checks while compiling. The program runs noticeably slower and uses more memory, so use them in tests and debug builds, not in production.
-fsanitize=address(ASan): out-of-bounds access, use after free, double free, and leaks.-fsanitize=undefined(UBSan): signed overflow, bad shifts, null pointer use and other undefined behavior.-fsanitize=thread(TSan): data races between threads. It cannot be combined with ASan, so make a separate build.
// bug.cpp
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v(3);
int *p = v.data();
std::cout << p[3] << '\n'; // one past the end: a bug
}
g++ -std=c++23 -g -O1 -fsanitize=address -fno-omit-frame-pointer bug.cpp -o bug
./bug
==2229131==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50200000001c ...
READ of size 4 at 0x50200000001c thread T0
#0 0x5586a01e5480 in main bug.cpp:9
Without ASan this program usually prints a random number and exits normally, which is why such bugs survive. In the CMake project above, enable the checks with cmake -S . -B build-asan -DENABLE_SANITIZERS=ON.
Third-party libraries
First check your distribution: most popular libraries are packaged with CMake support (for example sudo apt install libfmt-dev, then find_package(fmt CONFIG REQUIRED) and target_link_libraries(app PRIVATE fmt::fmt)). When you need versions your distribution does not have, or the same versions on every developer's machine, use a C++ package manager:
- vcpkg:
git clone https://github.com/microsoft/vcpkg.git, run./bootstrap-vcpkg.sh, setVCPKG_ROOT, thenvcpkg new --applicationandvcpkg add port fmtin your project. Configure CMake with-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmakeand it builds the libraries listed invcpkg.json. - Conan 2 (2.34 as of November 2026): run
conan profile detectonce, list packages under[requires]inconanfile.txtwithCMakeDepsandCMakeToolchainunder[generators], thenconan install . --output-folder=build --build=missingand configure CMake with-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmakefrom thebuilddirectory.
To build shared libraries of your own, see the Program Library HOWTO.
Mistakes to avoid
- Mixing standards between parts of a program without checking: link objects built with the same compiler family and standard library.
- Ignoring warnings.
-Wall -Wextracatches uninitialized variables, sign mistakes and missing returns; add-Werrorin CI. - Raw
newanddelete. Usestd::unique_ptr,std::make_uniqueand containers so memory is freed automatically. - Debugging an optimized build and wondering why variables show as "optimized out". Rebuild with
-O0or-Og.
Sources
- C++ standards support in GCC, gcc.gnu.org
- GCC home page (current releases), GCC 14 release notes (std::print) and GCC 16 release notes (C++20 by default)
- C++ support in Clang, clang.llvm.org
- Program instrumentation options (sanitizers, -fhardened), GCC manual
- cmake(1), CMake documentation
- CXX_STANDARD target property, CMake documentation
- cmake_minimum_required, CMake documentation
- g++ in Ubuntu 24.04 (noble), g++-14 in noble and g++ in Ubuntu 26.04 (resolute), Ubuntu Packages
- gcc package version in Fedora 45, Fedora mdapi
- Install and use packages with CMake, vcpkg documentation
- Build a simple CMake project using Conan, Conan 2 documentation