Chapter 7: Compiling C++ Sources with CMake
1. Understanding the Compilation Process
Key Points:
- Four-stage process: Preprocessing → Compilation → Assembly → Linking
- CMake abstracts low-level commands but allows granular control
- Toolchain configuration (compiler flags, optimizations, diagnostics)
Code Example - Basic Compilation Flow:
add_executable(MyApp main.cpp util.cpp)
2. Preprocessor Configuration
a. Include Directories
target_include_directories(MyAppPRIVATE src/${PROJECT_BINARY_DIR}/generated
)
b. Preprocessor Definitions
target_compile_definitions(MyAppPRIVATEDEBUG_MODE=1"PLATFORM_NAME=\"Linux\""
)
Key Considerations:
- Use
PRIVATE
/PUBLIC
/INTERFACE
appropriately - Avoid manual
-D
flags; prefer CMake’s abstraction
3. Optimization Configuration
a. General Optimization Levels
target_compile_options(MyAppPRIVATE$<$<CONFIG:RELEASE>:-O3>$<$<CONFIG:DEBUG>:-O0>
)
b. Specific Optimizations
target_compile_options(MyAppPRIVATE-funroll-loops-ftree-vectorize
)
Key Considerations:
- Use generator expressions for build-type-specific flags
- Test optimization compatibility with
check_cxx_compiler_flag()
4. Compilation Time Reduction
a. Precompiled Headers (PCH)
target_precompile_headers(MyAppPRIVATE<vector><string>common.h
)
b. Unity Builds
set(CMAKE_UNITY_BUILD ON)
set(CMAKE_UNITY_BUILD_BATCH_SIZE 50)
add_executable(MyApp UNITY_GROUP_SOURCES src/*.cpp)
Tradeoffs:
- PCH: Faster compilation but larger memory usage
- Unity Builds: Reduced link time but harder debugging
5. Diagnostics Configuration
a. Warnings & Errors
target_compile_options(MyAppPRIVATE-Wall-Werror-Wno-deprecated-declarations
)
b. Cross-Compiler Compatibility
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-Wconversion HAS_WCONVERSION)
if(HAS_WCONVERSION)target_compile_options(MyApp PRIVATE -Wconversion)
endif()
Best Practices:
- Treat warnings as errors in CI builds
- Use compiler-agnostic warning flags (
/W4
vs-Wall
)
6. Debug Information
target_compile_options(MyAppPRIVATE$<$<CONFIG:DEBUG>:-g3>$<$<CXX_COMPILER_ID:MSVC>:/Zi>
)
Key Considerations:
-g
(GCC/Clang) vs/Zi
(MSVC)- Separate debug symbols (
-gsplit-dwarf
for GCC)
7. Advanced Features
a. Link Time Optimization (LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported)
if(ipo_supported)set_target_properties(MyApp PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
b. Platform-Specific Flags
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")target_compile_options(MyApp PRIVATE -mfpu=neon)
endif()
8. Common Pitfalls & Solutions
Problem: Inconsistent flags across targets
Solution: Use add_compile_options()
carefully, prefer target-specific commands
Problem: Debug symbols missing in Release builds
Solution:
set(CMAKE_BUILD_TYPE RelWithDebInfo)
Problem: Compiler-specific flags breaking cross-platform builds
Solution: Use CMake’s abstraction:
target_compile_features(MyApp PRIVATE cxx_std_20)
9. Complete Example
cmake_minimum_required(VERSION 3.20)
project(OptimizedApp)# Compiler feature check
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-flto HAS_LTO)# Executable with unified build
add_executable(MyApp [UNITY_GROUP_SOURCES]src/main.cpp src/utils.cpp
)# Precompiled headers
target_precompile_headers(MyApp PRIVATE common.h)# Includes & definitions
target_include_directories(MyAppPRIVATEinclude/${CMAKE_CURRENT_BINARY_DIR}/gen
)target_compile_definitions(MyAppPRIVATEAPP_VERSION=${PROJECT_VERSION}
)# Optimization & diagnostics
target_compile_options(MyAppPRIVATE$<$<CONFIG:Release>:-O3 -flto>$<$<CONFIG:Debug>:-O0 -g3>-Wall-Werror
)# LTO configuration
if(HAS_LTO)set_target_properties(MyApp PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
Key Takeaways
- Target-Specific Commands > Global settings
- Generator Expressions enable conditional logic
- Compiler Abstraction ensures portability
- Diagnostic Rigor prevents runtime errors
- Build-Type Awareness (Debug/Release) is crucial
Multiple Choice Questions
Question 1: Compilation Stages
Which of the following are required stages in the C++ compilation process when using CMake?
A) Preprocessing
B) Linking
C) Assembly
D) Code generation
E) Static analysis
Question 2: Preprocessor Configuration
Which CMake commands are valid for configuring the preprocessor?
A) target_include_directories()
B) add_definitions(-DDEBUG)
C) target_compile_definitions()
D) include_directories()
E) target_link_libraries()
Question 3: Header File Management
Which CMake features help manage header files correctly?
A) Using target_include_directories()
with the PUBLIC
keyword
B) Manually copying headers to the build directory
C) Using configure_file()
to generate versioned headers
D) Adding headers to add_executable()
/add_library()
commands
E) Using file(GLOB)
to collect headers
Question 4: Optimizations
Which optimization techniques can be controlled via CMake?
A) Function inlining (-finline-functions
)
B) Loop unrolling (-funroll-loops
)
C) Link-Time Optimization (-flto
)
D) Setting -O3
as the default optimization level
E) Disabling exceptions via -fno-exceptions
Question 5: Reducing Compilation Time
Which CMake mechanisms are valid for reducing compilation time?
A) Enabling precompiled headers with target_precompile_headers()
B) Using UNITY_BUILD
to merge source files
C) Disabling RTTI via -fno-rtti
D) Enabling ccache
via CMAKE_<LANG>_COMPILER_LAUNCHER
E) Setting CMAKE_BUILD_TYPE=Debug
Question 6: Debugging Configuration
Which CMake settings are essential for generating debuggable binaries?
A) add_compile_options(-g)
B) set(CMAKE_BUILD_TYPE Debug)
C) target_compile_definitions(DEBUG)
D) Enabling -O0
optimization
E) Using -fsanitize=address
Question 7: Precompiled Headers
Which practices ensure correct usage of precompiled headers (PCH) in CMake?
A) Including PCH as the first header in source files
B) Using target_precompile_headers()
with PRIVATE
scope
C) Adding all headers to the PCH
D) Avoiding PCH for template-heavy code
E) Manually compiling headers with -x c++-header
Question 8: Error/Warning Flags
Which CMake commands enforce strict error handling?
A) add_compile_options(-Werror)
B) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
C) target_compile_options(-Wpedantic)
D) cmake_minimum_required(VERSION 3.10)
E) include(CheckCXXCompilerFlag)
Question 9: Platform-Specific Compilation
Which CMake variables detect platform-specific properties?
A) CMAKE_SYSTEM_NAME
B) CMAKE_CXX_COMPILER_ID
C) CMAKE_HOST_SYSTEM_PROCESSOR
D) CMAKE_SOURCE_DIR
E) CMAKE_ENDIANNESS
Question 10: Compiler Feature Detection
Which CMake modules/commands help check compiler support for C++ features?
A) check_cxx_compiler_flag()
B) include(CheckCXXSourceCompiles)
C) target_compile_features()
D) find_package(CXX17)
E) try_compile()
Answers & Explanations
Question 1: Compilation Stages
Correct Answers: A, C
- A) Preprocessing and C) Assembly are core stages.
- B) Linking occurs after compilation (handled separately).
- D) Code generation is part of the compilation stage.
- E) Static analysis is optional and not a standard stage.
Question 2: Preprocessor Configuration
Correct Answers: A, C
- A)
target_include_directories()
sets include paths. - C)
target_compile_definitions()
adds preprocessor macros. - B/D)
add_definitions()
andinclude_directories()
are legacy (not target-specific). - E)
target_link_libraries()
handles linking, not preprocessing.
Question 3: Header File Management
Correct Answers: A, C
- A)
target_include_directories(PUBLIC)
propagates include paths. - C)
configure_file()
generates headers (e.g., version info). - B/D/E) Manual copying,
add_executable()
, orfile(GLOB)
are error-prone.
Question 4: Optimizations
Correct Answers: A, B, C
- A/B/C) Explicitly controlled via
target_compile_options()
. - D)
-O3
is compiler-specific; CMake usesCMAKE_BUILD_TYPE
(e.g.,Release
). - E) Disabling exceptions is a language feature, not an optimization.
Question 5: Reducing Compilation Time
Correct Answers: A, B, D
- A/B) PCH and Unity builds reduce redundant parsing.
- D)
ccache
caches object files. - C/E) Disabling RTTI/Debug builds do not reduce compilation time directly.
Question 6: Debugging Configuration
Correct Answers: A, B, D
- A/B/D)
-g
,Debug
build type, and-O0
ensure debuggable binaries. - C)
DEBUG
is a preprocessor macro (not required for symbols). - E) Sanitizers aid debugging but are not strictly required.
Question 7: Precompiled Headers
Correct Answers: A, B
- A) PCH must be included first to avoid recompilation.
- B)
PRIVATE
limits PCH to the current target. - C/E) Including all headers or manual compilation breaks portability.
- D) PCH works with templates but may increase build complexity.
Question 8: Error/Warning Flags
Correct Answers: A, B, C
- A/B/C) Enforce warnings/errors via compiler flags.
- D/E)
cmake_minimum_required()
andCheckCXXCompilerFlag
are unrelated to error handling.
Question 9: Platform-Specific Compilation
Correct Answers: A, B, C
- A/B/C) Detect OS, compiler, and architecture.
- D)
CMAKE_SOURCE_DIR
is the project root. - E)
CMAKE_ENDIANNESS
is not a standard CMake variable.
Question 10: Compiler Feature Detection
Correct Answers: A, B, E
- A/B/E) Directly check compiler support for flags/features.
- C)
target_compile_features()
specifies required standards. - D)
CXX17
is not a standard package.
Practice Questions
Question 1: Preprocessor Definitions & Conditional Compilation
Scenario:
You’re working on a cross-platform project where a DEBUG_MODE
macro must be defined only in Debug builds, and a PLATFORM_WINDOWS
macro should be defined automatically when compiling on Windows. Additionally, in Release builds, the NDEBUG
macro must be enforced.
Task:
Write a CMake snippet to configure these preprocessor definitions correctly for a target my_app
, using modern CMake practices. Handle platform detection and build type conditions appropriately.
Question 2: Precompiled Headers (PCH)
Scenario:
Your project has a frequently used header common.h
that includes heavy template code. To speed up compilation, you want to precompile this header for a target my_lib
. However, your team uses both GCC/Clang and MSVC compilers.
Task:
Configure CMake to generate a PCH for common.h
and apply it to my_lib
, ensuring compatibility across GCC, Clang, and MSVC. Avoid hardcoding compiler-specific flags.
Hard Difficulty Question: Unity Builds & Platform-Specific Optimizations
Scenario:
A large project suffers from long compilation times. You decide to implement Unity Builds (combining multiple .cpp
files into a single compilation unit) for a target big_target
, while also enabling Link-Time Optimization (LTO) in Release builds. Additionally, on Linux, you want to enforce -march=native
, but on Windows, use /arch:AVX2
.
Task:
- Configure CMake to enable Unity Builds for
big_target
by grouping all.cpp
files insrc/
into batches of 10 files. - Enable LTO in Release builds using CMake’s built-in support.
- Apply architecture-specific optimizations conditionally based on the platform.
- Ensure the solution avoids
file(GLOB)
anti-patterns and uses generator expressions where appropriate.
Answers & Explanations
Answer to Medium Question 1
target_compile_definitions(my_appPRIVATE$<$<CONFIG:Debug>:DEBUG_MODE>$<$<PLATFORM_ID:Windows>:PLATFORM_WINDOWS>PUBLIC$<$<CONFIG:Release>:NDEBUG>
)
Explanation:
- Conditional Definitions:
- Use generator expressions (
$<...>
) to conditionally define macros based on build type (CONFIG
) and platform (PLATFORM_ID
). $<$<CONFIG:Debug>:DEBUG_MODE>
adds-DDEBUG_MODE
only in Debug builds.$<$<PLATFORM_ID:Windows>:PLATFORM_WINDOWS>
automatically definesPLATFORM_WINDOWS
on Windows.
- Use generator expressions (
- Public vs Private:
NDEBUG
is markedPUBLIC
to propagate to dependent targets (e.g., ifmy_app
is a library).- Platform-specific and build-type-specific flags are
PRIVATE
to avoid leaking to dependents.
Answer to Medium Question 2
# Enable precompiled headers for the target
target_precompile_headers(my_libPRIVATE# For GCC/Clang: Use the header directly$<$<CXX_COMPILER_ID:GNU,Clang>:common.h># For MSVC: Use forced include$<$<CXX_COMPILER_ID:MSVC>:/FIcommon.h>
)# MSVC requires the header to be part of the source tree
if(MSVC)target_sources(my_lib PRIVATE common.h)
endif()
Explanation:
- Compiler-Agnostic PCH:
target_precompile_headers
is the modern CMake way to handle PCH.- For GCC/Clang, specifying
common.h
directly tells CMake to precompile it. - MSVC requires
/FI
(Force Include) to use the PCH, hence the generator expression.
- MSVC Workaround:
- MSVC needs the header in the source list to avoid “header not found” errors.
- No Hardcoded Flags:
- Avoids manual
-Winvalid-pch
or/Yc
//Yu
flags by relying on CMake abstractions.
- Avoids manual
Answer to Hard Question
# 1. Unity Build Configuration
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS src/*.cpp)
set(BATCH_SIZE 10)
set(UNITY_SOURCES "")
math(EXPR N_BATCHES "${SRC_FILES} / ${BATCH_SIZE} + 1")foreach(BATCH RANGE 1 ${N_BATCHES})list(SUBLIST SRC_FILES ${BATCH_SIZE}*(BATCH-1) ${BATCH_SIZE} BATCH_FILES)if(BATCH_FILES)set(UNITY_FILE "unity_${BATCH}.cpp")file(WRITE ${UNITY_FILE} "")foreach(SRC ${BATCH_FILES})file(APPEND ${UNITY_FILE} "#include \"${SRC}\"\n")endforeach()list(APPEND UNITY_SOURCES ${UNITY_FILE})endif()
endforeach()add_library(big_target ${UNITY_SOURCES})# 2. LTO in Release
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)# 3. Platform-Specific Optimizations
target_compile_options(big_targetPRIVATE$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:-march=native>$<$<AND:$<PLATFORM_ID:Windows>,$<CONFIG:Release>>:/arch:AVX2>
)
Explanation:
- Unity Builds:
file(GLOB_RECURSE ... CONFIGURE_DEPENDS)
avoids the “stale file list” anti-pattern by re-globbing on build system regeneration.- Batches
.cpp
files intounity_X.cpp
files, each including 10 source files. - Note: Unity builds trade compilation speed for incremental build efficiency. Use judiciously.
- LTO:
CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE
enables LTO portably across compilers.
- Platform-Specific Flags:
- Uses nested generator expressions to apply
-march=native
on Linux and/arch:AVX2
on Windows only in Release builds.
- Uses nested generator expressions to apply
- Best Practices:
- Avoids
file(GLOB)
for source lists in most cases but usesCONFIGURE_DEPENDS
to mitigate its drawbacks here. - Generator expressions ensure flags are applied conditionally without polluting other configurations/platforms.
- Avoids
Key Concepts from Chapter 7 Reinforced:
- Generator Expressions: Used extensively for conditional logic based on build type, platform, and compiler.
- Precompiled Headers: Leveraged via
target_precompile_headers
with compiler-specific logic abstracted by CMake. - Build Optimization: Unity builds and LTO demonstrate advanced techniques to reduce compilation time.
- Platform/Compiler Portability: Solutions avoid hardcoding flags, using CMake variables (
PLATFORM_ID
,CXX_COMPILER_ID
) instead. - Modern CMake Practices: Use of
target_*
commands ensures properties propagate correctly to dependents.