20 topics
← Back to Quick Reference/
Topic 19
CCoommppiillee && RRuunn
Pipeline · Flags · Sanitizers · CMake · Debugging · Makefiles
C++17 · Advanced ReferenceCompilation Pipeline
01From 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.
Preprocessing— expands#include,#define, conditional compilation. Output is plain C++ text. - 2.
Compilation— parses C++, type-checks, generates assembly. Each.cppis compiled independently. - 3.
Assembly— converts assembly text to machine-code object files (.o/.obj). - 4.
Linking— resolves cross-file references, combines objects and libraries into one executable.
Common errors by stage
- 1.
Preprocessor: file not found, missing include guard, macro redefinition. - 2.
Compiler: syntax errors, type mismatches, undeclared identifiers — line numbers are accurate. - 3.
Linker: undefined reference — function declared but not defined anywhere; multiple definition — ODR violation. - 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++17 | Enable C++17. Use -std=c++20 for ranges, concepts, format. Always specify explicitly. |
| -Wall -Wextra | Enable core and extra warnings. Together they catch ~80% of common mistakes at compile time. |
| -Werror | Treat all warnings as errors. Enforces a zero-warning policy in the codebase. |
| -O2 | Standard release optimization. -O3 is rarely faster and can increase binary size significantly. |
| -DNDEBUG | Disables 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
| ASan | AddressSanitizer — heap/stack OOB, use-after-free, leaks. ~2× overhead. Enable in CI. |
| UBSan | UBSanitizer — signed overflow, null deref, misalignment. Very low overhead (~1.1×). |
| TSan | ThreadSanitizer — data races. Cannot combine with ASan. Run multithreaded tests separately. |
| -O1 | Use -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_required | Always specify — CMake behavior changes significantly between versions. |
| CXX_EXTENSIONS OFF | Disables GNU extensions (__int128, VLAs, etc.) for strict standard conformance. |
| target_compile_options | Apply flags to a specific target, not globally. Prefer target_* over global set() commands. |
| BUILD_TYPE | Debug / 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 / b | Set a breakpoint at a function name or file:line. Execution pauses there. |
| next / n | Execute the next line, stepping over function calls. |
| step / s | Execute the next line, stepping into function calls. |
| backtrace | Show the call stack at the current point — essential for diagnosing crashes. |
| print / p | Print 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: %.cpp | Pattern rule — matches any .o target and compiles from the matching .cpp. |
| .PHONY | Declares 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.