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.
# 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.cppStandards support
| Standard | Status | Flag |
|---|---|---|
| C++11 | Full | -std=c++11 |
| C++14 | Full | -std=c++14 |
| C++17 | Full | -std=c++17 |
| C++20 | Full (GCC 13+) | -std=c++20 |
| C++23 | Partial (GCC 14+) | -std=c++23 |
| C++26 | In 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
# 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)
-WerrorOptimization flags
-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
-g # Include debug info (DWARF)
-g3 # Include macro definitions too
-ggdb # GDB-specific extensions
-fno-omit-frame-pointer # Keep frame pointer for profilers/sanitizersLink-time optimization
# 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.cppArchitecture targets
GCC supports more target architectures than any other compiler:
# 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.cppSanitizers
# 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-pointerNote: ASan and TSan are mutually exclusive.
Useful diagnostics flags
# 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-asmCMake integration
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
| Situation | Prefer |
|---|---|
| Linux target, embedded (ARM/RISC-V) | GCC |
| macOS development | Clang |
| Best error messages | Clang |
| Widest architecture support | GCC |
| clang-tidy / clang-format | Clang |
| Maximum LTO performance on x86 | Similar |
| RISC-V, MIPS, PowerPC | GCC |
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 Version | C++11 | C++14 | C++17 | C++20 | C++23 | C++26 |
|---|---|---|---|---|---|---|
| GCC 7 | Full | Full | Partial | — | — | — |
| GCC 8 | Full | Full | Full | Partial | — | — |
| GCC 9 | Full | Full | Full | Partial | — | — |
| GCC 10 | Full | Full | Full | Partial | — | — |
| GCC 11 | Full | Full | Full | Partial | — | — |
| GCC 12 | Full | Full | Full | Full | Partial | — |
| GCC 13 | Full | Full | Full | Full | Partial | — |
| GCC 14 | Full | Full | Full | Full | Partial | Experimental |
| GCC 15 | Full | Full | Full | Full | Substantial | Experimental |
# 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 extensionsAvoid 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:
g++ -v # shows GCC version
g++ -std=c++23 -x c++ - < /dev/null 2>&1 | head -5 # quick standard flag testOptimization 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
-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
# 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 4Floating-point flags
# 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
-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.Link-Time Optimization (LTO)
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.
# 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 hereLinker plugin
# 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.cppCMake integration
# 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
# 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 runsWhere profile data lands
By default, GCC writes .gcda files next to the source files. Override with:
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.cppPGO with 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.
# 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 methodsRecommended compound flag sets
# 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=1024GCC-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
// 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
// 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
// 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
// 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
// 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)
# 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-gnuCross-compile invocation
# 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/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:
cmake -B build-arm64 \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-aarch64.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build-arm64Testing cross-compiled binaries with QEMU
# 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 automaticallyCMake 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
# 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_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
# 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
# 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}")