C++ Programming HOWTO: g++, clang++, CMake and debugging on Linux

Updated November 2026

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

Choosing the C++ standard
StandardGCC flagClang flagStatus (as of November 2026)
C++17-std=c++17-std=c++17Complete; Clang's default (as gnu++17) and GCC's default in versions 11 to 15
C++20-std=c++20-std=c++20GCC's default (as gnu++20) since GCC 16; modules still experimental in GCC
C++23-std=c++23-std=c++23Usable; 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:

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.

// 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:

To build shared libraries of your own, see the Program Library HOWTO.

Mistakes to avoid

Sources