20 topics
/
← Back to Quick Reference
Topic 19

Compile & Run

Pipeline · Flags · Sanitizers · CMake · Debugging · Makefiles

C++17 · Advanced Reference

Compilation Pipeline

01

From source to executable

C++ compilation is a four-stage pipeline. Understanding each stage helps you interpret error messages, choose the right flags, and understand why headers must be included in every translation unit that uses them.

The four stages

  1. 1.Preprocessing — expands #include, #define, conditional compilation. Output is plain C++ text.
  2. 2.Compilation — parses C++, type-checks, generates assembly. Each .cpp is compiled independently.
  3. 3.Assembly — converts assembly text to machine-code object files (.o / .obj).
  4. 4.Linking — resolves cross-file references, combines objects and libraries into one executable.

Common errors by stage

  1. 1.Preprocessor: file not found, missing include guard, macro redefinition.
  2. 2.Compiler: syntax errors, type mismatches, undeclared identifiers — line numbers are accurate.
  3. 3.Linker: undefined reference — function declared but not defined anywhere; multiple definition — ODR violation.
  4. 4.Runtime: segfault, assertion failure, exception — use sanitizers and debugger to trace.

Linker errors reference mangled symbol names. Tools like c++filt can demangle them: echo "_ZN3FooC1Ev" | c++filt → Foo::Foo()

# The four stages of compilation

# 1. Preprocessing — expands #include, #define, #if
g++ -E main.cpp -o main.i       # output: preprocessed source

# 2. Compilation — source → assembly
g++ -S main.i -o main.s         # output: assembly (.s)

# 3. Assembly — assembly → object file
g++ -c main.cpp -o main.o       # output: object file (.o)

# 4. Linking — object files + libraries → executable
g++ main.o utils.o -o myapp     # output: executable

# All at once (usual workflow)
g++ -std=c++17 main.cpp -o myapp

# Multiple source files
g++ -std=c++17 main.cpp utils.cpp math.cpp -o myapp

Compiler Flags

02
# ── Standard version ──────────────────────────────────────────
g++ -std=c++17   # C++17 (recommended minimum)
g++ -std=c++20   # C++20 (ranges, format, concepts)
g++ -std=c++23   # C++23 (latest)

# ── Warning flags ──────────────────────────────────────────────
-Wall            # core warnings (unused vars, implicit fallthrough, …)
-Wextra          # additional warnings on top of -Wall
-Wpedantic       # reject non-standard extensions
-Wconversion     # warn on implicit narrowing conversions
-Wshadow         # warn when a local shadows an outer variable
-Werror          # treat all warnings as errors

# ── Optimization ──────────────────────────────────────────────
-O0   # no optimization (fastest compile, easiest to debug)
-O1   # basic optimizations
-O2   # standard release build — good balance
-O3   # aggressive (may increase binary size)
-Os   # optimize for binary size
-Og   # debug-friendly optimization (GCC) — better than -O0 for debugging

# ── Debug symbols ─────────────────────────────────────────────
-g    # include DWARF debug info (gdb / lldb)
-g3   # include macro definitions too

# ── Recommended builds ─────────────────────────────────────────
# Development:
g++ -std=c++17 -O1 -g -fsanitize=address,undefined -Wall -Wextra -Werror
# Release:
g++ -std=c++17 -O2 -DNDEBUG -Wall -Wextra -Werror
-std=c++17Enable C++17. Use -std=c++20 for ranges, concepts, format. Always specify explicitly.
-Wall -WextraEnable core and extra warnings. Together they catch ~80% of common mistakes at compile time.
-WerrorTreat all warnings as errors. Enforces a zero-warning policy in the codebase.
-O2Standard release optimization. -O3 is rarely faster and can increase binary size significantly.
-DNDEBUGDisables assert() in release builds. Define alongside -O2 for production.

Runtime Sanitizers

03
# ── AddressSanitizer (ASan) ───────────────────────────────────
g++ -fsanitize=address -g -O1 main.cpp -o app
# Detects: heap/stack OOB, use-after-free, double-free, leaks
# Overhead: ~2× memory, ~2× runtime

# ── UBSanitizer (UBSan) ───────────────────────────────────────
g++ -fsanitize=undefined -g -O1 main.cpp -o app
# Detects: signed overflow, null deref, misaligned access, invalid cast
# Overhead: ~1.1× (very low)

# ── ThreadSanitizer (TSan) — combine with caution ─────────────
g++ -fsanitize=thread -g -O1 main.cpp -o app
# Detects: data races between threads
# ⚠ Incompatible with ASan — run separately

# ── Combine ASan + UBSan ──────────────────────────────────────
g++ -fsanitize=address,undefined -g -O1 main.cpp -o app

# ── Control sanitizer behavior at runtime ─────────────────────
ASAN_OPTIONS=detect_leaks=1 ./app
UBSAN_OPTIONS=print_stacktrace=1 ./app
ASanAddressSanitizer — heap/stack OOB, use-after-free, leaks. ~2× overhead. Enable in CI.
UBSanUBSanitizer — signed overflow, null deref, misalignment. Very low overhead (~1.1×).
TSanThreadSanitizer — data races. Cannot combine with ASan. Run multithreaded tests separately.
-O1Use -O1 not -O0 with sanitizers — some bugs only appear with minimal optimization and the error messages are clearer.
Add sanitizers to your CI pipeline. -fsanitize=address,undefined on every test run catches the majority of memory and UB bugs before they reach production.

CMake

04
# CMakeLists.txt — minimal modern CMake project
cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)   # disable GNU extensions

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

target_compile_options(myapp PRIVATE
    -Wall -Wextra -Wpedantic
)

# Build & run
# mkdir build && cd build
# cmake ..
# cmake --build .
# ./myapp

# Debug build with sanitizers
# cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" ..

# Release build
# cmake -DCMAKE_BUILD_TYPE=Release ..
cmake_minimum_requiredAlways specify — CMake behavior changes significantly between versions.
CXX_EXTENSIONS OFFDisables GNU extensions (__int128, VLAs, etc.) for strict standard conformance.
target_compile_optionsApply flags to a specific target, not globally. Prefer target_* over global set() commands.
BUILD_TYPEDebug / Release / RelWithDebInfo / MinSizeRel. Set at configure time with -DCMAKE_BUILD_TYPE=Release.

Debugging with GDB & LLDB

05
# Compile with debug symbols
g++ -g -O0 main.cpp -o app

# ── GDB basics ────────────────────────────────────────────────
gdb ./app             # start debugger
run                   # run the program
run arg1 arg2         # run with arguments

break main            # set breakpoint at function
break main.cpp:42     # set breakpoint at line 42
info breakpoints      # list all breakpoints
delete 1              # delete breakpoint 1

next    (n)           # execute next line (step over)
step    (s)           # step into function
finish                # run until current function returns
continue (c)          # continue to next breakpoint

print x               # print variable value
print *ptr            # print value at pointer
info locals           # print all local variables
backtrace (bt)        # show call stack

# ── LLDB (macOS) equivalents ─────────────────────────────────
lldb ./app
breakpoint set --name main
process launch
thread step-over   (n)
thread step-in     (s)
frame variable     # info locals equivalent
break / bSet a breakpoint at a function name or file:line. Execution pauses there.
next / nExecute the next line, stepping over function calls.
step / sExecute the next line, stepping into function calls.
backtraceShow the call stack at the current point — essential for diagnosing crashes.
print / pPrint the value of any expression, variable, or dereferenced pointer.

Makefile Basics

06
# Makefile — simple build system for small projects
CXX      = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -Werror -O2

# Default target
all: myapp

# Link
myapp: main.o utils.o
	$(CXX) $(CXXFLAGS) -o $@ $^

# Compile (pattern rule)
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c -o $@ $<

# Clean
clean:
	rm -f *.o myapp

.PHONY: all clean

# Usage:
# make          — build
# make clean    — remove build artifacts
# make -j4      — build with 4 parallel jobs
$@The target name of the current rule.
$^All prerequisites (dependencies) of the current rule.
$<The first prerequisite — typically the source file in a compile rule.
%.o: %.cppPattern rule — matches any .o target and compiles from the matching .cpp.
.PHONYDeclares targets that are not files (all, clean). Prevents conflicts with same-named files.
Use CMake for anything beyond a few files. Makefiles are fine for small projects, but CMake handles dependencies, cross-platform builds, and IDE integration automatically.