Skip to content
C++
Compiler
Updated 2026-05-01T00:00:00.000Z

GCC

GNU Compiler Collection — the workhorse of open-source C++ development. Widest platform and architecture support, mature optimizer, de-facto Linux standard.

TL;DR

GCC is the de-facto C++ compiler on Linux and the only serious choice for many embedded architectures. Use GCC 13+ for full C++23 partial support and GCC 14+ for improved C++23 conformance. Prefer -O2 for release builds and always pair with ASan/UBSan during development.

bash
# Install (Ubuntu/Debian)
sudo apt install gcc-14 g++-14

# Compile
g++-14 -std=c++23 -O2 -Wall -Wextra -o myapp main.cpp

# With sanitizers (dev builds)
g++-14 -std=c++20 -O1 -fsanitize=address,undefined -fno-omit-frame-pointer -o myapp main.cpp

Standards support

StandardStatusFlag
C++11Full-std=c++11
C++14Full-std=c++14
C++17Full-std=c++17
C++20Full (GCC 13+)-std=c++20
C++23Partial (GCC 14+)-std=c++23
C++26In progress-std=c++26

Use --std=c++20 (or c++17) as your minimum in new projects. Avoid c++0x/c++1y — those are legacy spellings.

Essential flags

Warning flags

bash
# Recommended baseline
-Wall -Wextra -Wpedantic

# Stronger — catches more real bugs
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion \
  -Wnull-dereference -Wdouble-promotion -Wformat=2

# For header-only libs (treat warnings as errors)
-Werror

Optimization flags

bash
-O0    # No optimization — fast compilation, best for debugging
-O1    # Minimal optimization — good for sanitizer builds
-O2    # Standard release — best balance (use this)
-O3    # Aggressive — more vectorization, sometimes slower due to code size
-Os    # Optimize for size (embedded)
-Og    # Debug-friendly optimization (GCC only)

Debugging

bash
-g        # Include debug info (DWARF)
-g3       # Include macro definitions too
-ggdb     # GDB-specific extensions
-fno-omit-frame-pointer  # Keep frame pointer for profilers/sanitizers
bash
# Enable LTO (major win for release builds)
g++ -O2 -flto -fuse-linker-plugin -o myapp main.cpp lib.cpp

# Thin LTO (parallel, faster than full LTO)
g++ -O2 -flto=auto -o myapp main.cpp lib.cpp

Architecture targets

GCC supports more target architectures than any other compiler:

bash
# Cross-compile for ARM (embedded)
arm-none-eabi-g++ -std=c++20 -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard \
  -Os -fno-exceptions -fno-rtti -o firmware.elf main.cpp

# RISC-V
riscv64-unknown-elf-g++ -march=rv64imafd -mabi=lp64d -o app main.cpp

# WebAssembly (via Emscripten, which uses LLVM internally but has GCC-compatible flags)
em++ -std=c++20 -O2 -o app.wasm main.cpp

Sanitizers

bash
# AddressSanitizer (heap/stack buffer overflows, use-after-free)
-fsanitize=address

# UndefinedBehaviorSanitizer (signed overflow, null deref, etc.)
-fsanitize=undefined

# ThreadSanitizer (data races)
-fsanitize=thread

# Combine ASan + UBSan (most useful combination)
-fsanitize=address,undefined -fno-omit-frame-pointer

Note: ASan and TSan are mutually exclusive.

Useful diagnostics flags

bash
# Show include paths resolved
-H

# Show where a macro is defined
-fdump-macro-expansion

# Show compilation time breakdown
-ftime-report

# Preprocessor output only
-E

# Assembly output (readable)
-S -fverbose-asm

CMake integration

cmake
cmake_minimum_required(VERSION 3.20)
project(myapp CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(myapp main.cpp)

# Apply warning flags
target_compile_options(myapp PRIVATE
  $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -Wpedantic -Wshadow>
)

# Enable LTO for Release builds
set_target_properties(myapp PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)

GCC vs Clang — when to prefer GCC

SituationPrefer
Linux target, embedded (ARM/RISC-V)GCC
macOS developmentClang
Best error messagesClang
Widest architecture supportGCC
clang-tidy / clang-formatClang
Maximum LTO performance on x86Similar
RISC-V, MIPS, PowerPCGCC

In practice, testing against both GCC and Clang in CI is the best approach — they catch different issues.

Common pitfalls

Implicit function declarations: GCC historically warned about these; in C23 they're errors. Always use proper headers.

Integer overflow: GCC assumes signed integer overflow doesn't happen (UB). Use -fwrapv only if you need wrap-around semantics.

Strict aliasing: GCC optimizes based on strict aliasing rules. Use -fno-strict-aliasing with caution — it disables a real optimization.

-O3 regressions: -O3 enables more aggressive transformations that can hurt performance due to code size increases. Profile before choosing -O3 over -O2.

C++ Standards Support Matrix

GCC's standards support is the most complete of any open-source compiler. Each GCC major release ships measurably more of the next standard than the last; the table below reflects production-ready support, not experimental flags.

GCC VersionC++11C++14C++17C++20C++23C++26
GCC 7FullFullPartial———
GCC 8FullFullFullPartial——
GCC 9FullFullFullPartial——
GCC 10FullFullFullPartial——
GCC 11FullFullFullPartial——
GCC 12FullFullFullFullPartial—
GCC 13FullFullFullFullPartial—
GCC 14FullFullFullFullPartialExperimental
GCC 15FullFullFullFullSubstantialExperimental
bash
# C++11 through C++23 — stable spelling
g++ -std=c++11 ...
g++ -std=c++14 ...
g++ -std=c++17 ...
g++ -std=c++20 ...
g++ -std=c++23 ...

# C++26 draft (GCC 14+) — uses transitional flag
g++ -std=c++2c ...    # accepted alias while standard is still being finalized
# GCC 15 also accepts -std=c++26

# GNU extensions (default when no -std is given — avoid for portable code)
g++ -std=gnu++23 ...  # C++23 + GNU extensions

Avoid legacy flags like -std=c++0x (C++11 draft) and -std=c++1y (C++14 draft). They still compile but are confusing and may behave differently in edge cases. Always use the final year spelling once the standard is ratified.

Check what your installed GCC supports:

bash
g++ -v                        # shows GCC version
g++ -std=c++23 -x c++ - < /dev/null 2>&1 | head -5   # quick standard flag test

Optimization Deep Dive

GCC's optimizer is mature and aggressive — understand the tradeoffs before reaching for -O3. Most projects ship with -O2 for good reason.

Optimization levels

bash
-O0    # No optimization. Fastest compilation. Every C++ statement maps 1:1 to
       # instructions. Best for step-through debugging.

-O1    # ~50 fast passes: dead code elimination, basic inlining, register
       # allocation. Good for sanitizer builds (ASan/UBSan work best here).

-O2    # The production default. Adds vectorization, loop unrolling, CSE,
       # instruction scheduling. The right choice for almost all release builds.

-O3    # Adds more aggressive loop transformations, more inlining, function
       # cloning. Can be slower than -O2 when code size growth blows I-cache.
       # Always benchmark; do not assume faster.

-Os    # Like -O2 but disables transformations that increase code size.
       # Best for embedded targets with tight flash constraints.

-Oz    # Clang-only; GCC uses -Os for size. Included here for reference.

-Og    # Debug-friendly optimization (GCC-specific). Superset of -O1 with
       # extra passes that don't hinder debugging. Better than -O0 for
       # catching optimizer-exposed bugs while keeping debug info usable.

Architecture tuning

bash
# Tune for the machine compiling the binary — never ship these binaries to other hosts
-march=native        # Enable all ISA extensions the build host supports (AVX2, etc.)
-mtune=native        # Schedule for build host's microarchitecture, keep ABI portable

# Target a specific micro-arch for distribution
-march=x86-64-v3     # AVX2 baseline — safe for most 2015+ x86 machines
-march=armv8-a       # Generic ARMv8 (Cortex-A53 and up)
-mcpu=cortex-a72     # ARM: sets both -march and -mtune equivalent

# Separate march from mtune
-march=x86-64 -mtune=znver4   # Compile for any x86-64, schedule for AMD Zen 4

Floating-point flags

bash
# CAUTION: -ffast-math breaks IEEE 754 compliance
-ffast-math          # Enables: -fno-math-errno, -funsafe-math-optimizations,
                     # -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans,
                     # -fcx-limited-range. NaN/Inf checks silently wrong.
                     # Fine for game physics; dangerous for finance or scientific code.

-fno-math-errno      # Safe subset: skip errno after math calls (most code never
                     # checks it). Enables vectorization of sqrt/sin/cos.

-fassociative-math   # Allows reordering floating-point ops — breaks numerical
                     # algorithms that depend on ordering (Kahan summation, etc.)

Frame pointer

bash
-fomit-frame-pointer   # GCC enables this at -O1+. Frees one register on x86.
                        # Breaks profilers (perf, gprof) and some stack unwinders.

-fno-omit-frame-pointer # Restore frame pointer. Required when using ASan or
                         # perf with frame-pointer-based unwinding.
                         # Tiny performance cost (~1%) worth paying in dev builds.

LTO lets the linker perform cross-translation-unit optimizations that the compiler cannot see. Typical results: 5–20% binary size reduction, 3–15% runtime speedup on CPU-bound code, at the cost of longer link times.

How LTO works

Without LTO, each .cpp file compiles to a .o with native machine code. The linker can only concatenate them. With LTO, the compiler embeds its intermediate representation (GIMPLE) inside the .o. The linker then calls back into GCC to optimize across all translation units simultaneously.

bash
# Full LTO — single-threaded link optimization (slow for large projects)
g++ -O2 -flto -o myapp main.cpp lib.cpp util.cpp

# Parallel LTO — uses all available cores during link
g++ -O2 -flto=auto -o myapp main.cpp lib.cpp util.cpp

# Explicit thread count
g++ -O2 -flto=8 -o myapp main.cpp lib.cpp util.cpp

# LTO requires the same flags at compile and link time
g++ -O2 -flto -c -o main.o main.cpp
g++ -O2 -flto -c -o lib.o lib.cpp
g++ -O2 -flto -o myapp main.o lib.o    # linker triggers LTO pass here

Linker plugin

bash
# Gold linker (GNU gold) with LTO plugin — faster linking than ld.bfd for large projects
g++ -O2 -flto -fuse-linker-plugin -fuse-ld=gold -o myapp main.cpp lib.cpp

# Or use lld (LLVM linker) which also supports GCC LTO bitcode
g++ -O2 -flto -fuse-ld=lld -o myapp main.cpp lib.cpp

CMake integration

cmake
# Option 1: Per-target (preferred — fine-grained control)
set_property(TARGET myapp PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)

# Option 2: Global (enables LTO on all targets)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)

# Option 3: Release-only LTO (avoids slow debug link times)
set_target_properties(myapp PROPERTIES
  INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE
  INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE
)

CMake 3.9+ checks CMAKE_INTERPROCEDURAL_OPTIMIZATION_SUPPORTED automatically — it will error on compilers that don't support LTO rather than silently ignoring the property.

Profile-Guided Optimization (PGO)

PGO is the highest-impact single-flag optimization GCC offers — real-world speedups of 10–25% are common. It works by running the binary with representative workloads, recording branch frequencies and call counts, then recompiling with that data to make better inlining and layout decisions.

Three-step PGO workflow

bash
# Step 1: Instrument binary — compiles with profiling hooks
g++ -O2 -fprofile-generate -o myapp_instrumented main.cpp lib.cpp

# Step 2: Run with representative workloads — generates .gcda profile files
./myapp_instrumented workload1.dat
./myapp_instrumented workload2.dat
./myapp_instrumented --benchmark

# Step 3: Recompile using collected profile data
g++ -O2 -fprofile-use=. -fprofile-correction -o myapp main.cpp lib.cpp
# -fprofile-use=<dir> points to the directory containing .gcda files
# -fprofile-correction handles mismatched counts from parallel runs

Where profile data lands

By default, GCC writes .gcda files next to the source files. Override with:

bash
g++ -O2 -fprofile-generate=/tmp/pgo-data -o myapp_inst main.cpp
./myapp_inst workload.dat
g++ -O2 -fprofile-use=/tmp/pgo-data -o myapp main.cpp

PGO with CMake

cmake
# Phase 1: instrument build
add_compile_options(-O2 -fprofile-generate)
add_link_options(-fprofile-generate)

# Phase 2: after running workloads, switch to:
add_compile_options(-O2 -fprofile-use=${CMAKE_SOURCE_DIR}/pgo-data -fprofile-correction)

Practical guidance: PGO pays off most on large binaries with hot inner loops (databases, compressors, parsers, game engines). For small CLI tools, the three-step overhead rarely justifies the gain.

Useful Warning Flags

Warnings are free static analysis. The GCC baseline -Wall -Wextra catches roughly 80% of common bugs; the extended set below catches the rest.

bash
# Tier 1 — always on, no false positives
-Wall              # ~30 individual warnings: uninitialized vars, unused results,
                   # implicit int, missing returns, etc.
-Wextra            # Additional: unused parameters, missing field initializers,
                   # sign comparison, empty body in if/else, etc.
-Wpedantic         # Strict ISO C++ conformance warnings (non-standard extensions)

# Tier 2 — highly recommended, rare false positives
-Wshadow           # Warns when a local variable shadows an outer-scope name.
                   # Catches subtle bugs: void f(int x) { int x = 0; ... }
-Wconversion       # Implicit conversions that may change value (int→char, etc.)
-Wsign-conversion  # Signed/unsigned implicit conversions specifically
-Wold-style-cast   # C-style casts (int*)ptr. Force static_cast/reinterpret_cast.
-Wnull-dereference # GCC flow-analysis: detects provably null pointer dereferences
-Wformat=2         # Stricter printf/scanf format string checking (includes =1 + more)

# Tier 3 — GCC-specific, high value
-Wduplicated-cond  # if (a) ... else if (a) ... — always-false else branch
-Wduplicated-branches  # if/else where both branches are identical (copy-paste bug)
-Wlogical-op       # Suspicious use of && or || where bitwise & or | was likely meant
-Wuseless-cast     # Casting a value to its own type: static_cast<int>(int_var)
-Wmisleading-indentation  # Indentation doesn't match block structure (duff's device)
-Wno-unused-parameter  # Suppress -Wextra's unused-param when you need stub methods
bash
# Development / CI baseline
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion \
  -Wnull-dereference -Wformat=2 -Wduplicated-cond -Wlogical-op

# Strict / library development (all of above + errors)
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion \
  -Wnull-dereference -Wformat=2 -Wduplicated-cond -Wlogical-op \
  -Wold-style-cast -Wuseless-cast -Werror

# Embedded / safety-critical
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion \
  -Wformat=2 -Wduplicated-cond -Wlogical-op -Werror \
  -Wstack-usage=512 -Wlarger-than=1024

GCC-Specific Attributes and Extensions

GCC attributes give fine-grained control over code generation that no standard C++ facility exposes. Use them behind #ifdef __GNUC__ guards in portable code.

Inlining hints

cpp
// Force inlining regardless of -O level or compiler heuristics
__attribute__((always_inline)) inline int fast_abs(int x) {
    return x < 0 ? -x : x;
}

// Prevent inlining — useful for error-path functions to keep hot code compact
__attribute__((noinline)) void handle_error(const char* msg) {
    fprintf(stderr, "Error: %s\n", msg);
    std::terminate();
}

Memory layout

cpp
// Pack struct to minimum size — no padding inserted
struct __attribute__((packed)) WireHeader {
    uint8_t  version;
    uint16_t length;   // sits at offset 1 (unaligned on most arches — beware!)
    uint32_t checksum; // sits at offset 3
};
// sizeof(WireHeader) == 7, not 8

// Explicit alignment
struct __attribute__((aligned(64))) CacheLine {
    double data[8];    // occupies exactly one 64-byte cache line
};

Branch prediction hints

cpp
// Tell GCC which branch is the common case
// __builtin_expect(expr, expected_value)
if (__builtin_expect(ptr == nullptr, 0)) {   // 0 = unlikely
    handle_null();
}

// C++20 standard equivalent (prefer this in new code)
[[likely]]   if (x > 0) { ... }
[[unlikely]] if (x == nullptr) { ... }

Unreachable paths

cpp
// Tell the optimizer a code path is never taken
// Allows better codegen — eliminates dead branches
switch (dir) {
    case North: return move_north();
    case South: return move_south();
    case East:  return move_east();
    case West:  return move_west();
    default:    __builtin_unreachable();  // no default case in your enum? say so.
}

// C++23 standard equivalent (prefer over __builtin_unreachable in new code)
std::unreachable();  // <utility>

Other useful attributes

cpp
// Mark function as pure (no side effects, result depends only on args + globals)
__attribute__((pure))  int compute(int x);

// Mark function as const (stricter: result depends only on args, reads no globals)
__attribute__((const)) int square(int x);

// Warn at call sites if return value is ignored (pre-C++17 [[nodiscard]])
__attribute__((warn_unused_result)) int fread_checked(...);

// Function is cold (rarely called) — optimize for size, move out of hot section
__attribute__((cold)) void dump_state();

// Visibility control for shared libraries
__attribute__((visibility("default"))) void public_api();
__attribute__((visibility("hidden")))  void internal_helper();

Cross-Compilation with GCC

GCC cross-compilation is a first-class use case — pre-built cross toolchains are available for every major embedded and Linux target. The prefix naming convention is arch-vendor-os-abi-g++.

Install cross toolchains (Debian/Ubuntu)

bash
# ARM 32-bit Linux (Raspberry Pi 2/3 in 32-bit mode, Cortex-A)
sudo apt install g++-arm-linux-gnueabihf

# ARM 64-bit Linux (Raspberry Pi 4/5, most modern SBCs)
sudo apt install g++-aarch64-linux-gnu

# RISC-V 64-bit Linux
sudo apt install g++-riscv64-linux-gnu

# ARM bare-metal (no OS — microcontrollers, Cortex-M)
sudo apt install gcc-arm-none-eabi

# MIPS Linux (routers, legacy embedded)
sudo apt install g++-mips-linux-gnu

Cross-compile invocation

bash
# ARM 32-bit Linux hard-float ABI
arm-linux-gnueabihf-g++ -std=c++20 -O2 \
  -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard \
  -o app_arm32 main.cpp

# ARM 64-bit Linux
aarch64-linux-gnu-g++ -std=c++23 -O2 -o app_arm64 main.cpp

# RISC-V 64-bit Linux
riscv64-linux-gnu-g++ -std=c++20 -O2 \
  -march=rv64imafdc -mabi=lp64d \
  -o app_riscv64 main.cpp

# Verify the output architecture
file app_arm64   # "ELF 64-bit LSB executable, ARM aarch64"

CMake toolchain file

Create a file cmake/toolchain-aarch64.cmake:

cmake
# cmake/toolchain-aarch64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

set(CROSS_PREFIX aarch64-linux-gnu-)
set(CMAKE_C_COMPILER   ${CROSS_PREFIX}gcc)
set(CMAKE_CXX_COMPILER ${CROSS_PREFIX}g++)
set(CMAKE_STRIP        ${CROSS_PREFIX}strip)

# Sysroot — optional but needed for linking against target libraries
set(CMAKE_SYSROOT /opt/sysroots/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})

# Only search in the sysroot for headers/libs, not the host machine
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Use it:

bash
cmake -B build-arm64 \
  -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-aarch64.cmake \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build-arm64

Testing cross-compiled binaries with QEMU

bash
# Install user-mode QEMU for the target arch
sudo apt install qemu-user qemu-user-static

# Run ARM64 binary transparently on x86 host
qemu-aarch64-static -L /usr/aarch64-linux-gnu ./app_arm64

# Or register binfmt_misc handlers so it's fully transparent
sudo apt install binfmt-support
./app_arm64    # runs via QEMU automatically

CMake Integration

GCC integrates with CMake through compiler detection, not explicit configuration — CMake auto-detects g++ when it's on PATH or when CMAKE_CXX_COMPILER is set. The patterns below cover the cases where you need explicit control.

Compiler selection

cmake
# Method 1: environment variable (set before cmake configure)
# $ CXX=g++-14 cmake -B build

# Method 2: CMake cache variable
cmake -B build -DCMAKE_CXX_COMPILER=g++-14

# Method 3: toolchain file (portable, checked into repo)
# cmake/toolchain-gcc14.cmake:
set(CMAKE_CXX_COMPILER g++-14)

Standards and compile options

cmake
cmake_minimum_required(VERSION 3.25)
project(myapp LANGUAGES CXX)

# Require C++20 — no silent fallback to older standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)   # -std=c++20 not -std=gnu++20

add_executable(myapp main.cpp src/engine.cpp)

# GCC-specific warnings — won't apply to Clang/MSVC builds
target_compile_options(myapp PRIVATE
  $<$<CXX_COMPILER_ID:GNU>:
    -Wall -Wextra -Wpedantic
    -Wshadow -Wconversion -Wsign-conversion
    -Wnull-dereference -Wformat=2
    -Wduplicated-cond -Wlogical-op
  >
)

LTO and PGO in CMake

cmake
# LTO — CMake 3.9+ checks compiler support automatically
set_property(TARGET myapp PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)

# Or enable only for Release/RelWithDebInfo
set_target_properties(myapp PROPERTIES
  INTERPROCEDURAL_OPTIMIZATION_RELEASE         TRUE
  INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO  TRUE
)

# PGO — Phase 1 (instrument)
target_compile_options(myapp PRIVATE
  $<$<CXX_COMPILER_ID:GNU>:-fprofile-generate=${CMAKE_BINARY_DIR}/pgo>
)
target_link_options(myapp PRIVATE
  $<$<CXX_COMPILER_ID:GNU>:-fprofile-generate=${CMAKE_BINARY_DIR}/pgo>
)

# PGO — Phase 2 (use profile, separate CMake configure)
target_compile_options(myapp PRIVATE
  $<$<CXX_COMPILER_ID:GNU>:
    -fprofile-use=${CMAKE_BINARY_DIR}/pgo
    -fprofile-correction
  >
)

Checking GCC version in CMake

cmake
# Guard features available only in GCC 12+
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
    CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.0")
  target_compile_options(myapp PRIVATE -Wduplicated-branches)
endif()

# Print detected compiler info
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
Edit on GitHubUpdated 2026-05-01T00:00:00.000Z