跳到主要内容
🔍
1.1.5已发布intermediate · 约 3 课时 · P0

CMake 入门与多目录项目

用现代 CMake(target 级命令)描述最小项目与多目录工程,理解配置/生成/构建三阶段与 out-of-source 构建

  • 来源
  • 补充
  • 纠错
  • 待确认

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.1.5 CMake 入门与多目录项目

本章来源:配置/生成/构建三阶段、CMakeLists 基本结构与多目录 add_subdirectory 来自《第一阶段讲义》5.x 节【来源】,经重新组织表述;-D 变量拼写笔误已【纠错】(err-7.1-23:-DMAKE_* → -DCMAKE_*);原资料示例全部以截图嵌入无法还原【待确认】——本章示例代码全部重写并本机实测【补充】(cmake 4.2.3,约 40% 新编);现代 target 级写法为资料未覆盖的补充内容。平台适用性 universal。

① 学习目标

  1. 说出 CMake 解决的"跨平台构建描述"问题,以及它与 Makefile 的关系;
  2. 讲清 配置(configure)→ 生成(generate)→ 构建(build)三阶段各自做什么;
  3. 写出最小项目的 CMakeLists.txt(cmake_minimum_required / project / add_executable)并完成一次 out-of-source 构建;
  4. 使用 target 级命令(target_include_directories / target_link_libraries)与 PRIVATE / PUBLIC / INTERFACE 表达依赖;
  5. 独立完成多目录工程(add_subdirectory + add_library + 链接);
  6. 用 -DCMAKE_BUILD_TYPE 切换 Debug/Release,并说出常见配置与链接错误各发生在哪一阶段。

② 前置知识

  • 必选:1.1.4 Makefile(构建规则与增量编译概念——CMake 是把"写规则"再抽象一层);
  • 建议:1.1.1 GCC 四阶段(理解编译与链接错误的阶段归属,本章诊断示例直接复用)。

③ 核心概念【来源】

概念说明
CMake 解决什么问题Makefile 直接描述"怎么编译",但换平台(Linux/Windows/macOS、不同生成器)要重写;CMake 用 CMakeLists.txt 描述"工程长什么样",由它生成对应平台的构建脚本
三阶段配置:读取 CMakeLists.txt 与 -D 选项,检测编译器/平台;② 生成:产出构建脚本(Unix Makefiles / MinGW Makefiles / Ninja / Visual Studio 工程);③ 构建:执行生成的脚本(make/ninja/MSBuild)产出可执行文件
cmake_minimum_required声明所需最低 CMake 版本(如 cmake_minimum_required(VERSION 3.16)),避免用低版本打开高版本工程时语义不一致;不锁定陈旧版本,写当前稳定可用的下限即可
projectproject(名称 C):命名工程并声明语言,同时定义 CMAKE_PROJECT_NAME 等变量
add_executable / add_library声明可执行目标 / 库目标(STATIC/SHARED);目标(target)是现代 CMake 的核心对象
target_include_directories某个目标加头文件搜索路径(替代全局 include_directories 旧式写法)
target_link_libraries某个目标链接库;同时"传递"库的 PUBLIC 使用要求(头文件路径、宏、依赖)
PRIVATE / PUBLIC / INTERFACE使用要求的传播方向:PRIVATE 只自己用;INTERFACE 只给下游用(纯头文件库);PUBLIC 两者都要(库本身和下游都需要)
out-of-source buildcmake -S 源目录 -B build:构建产物全部进 build/,源码目录零污染;是社区标准实践(in-source 已不推荐)
-DCMAKE_BUILD_TYPE配置单配置生成器(Makefiles/Ninja)的构建类型:Debug(-g 调试)/ Release(-O3 -DNDEBUG)/ RelWithDebInfo
cmake --build统一构建入口:cmake --build build(自动调用底层 make/ninja/MSBuild,跨平台一致)
生成器差异Unix Makefiles / MinGW Makefiles / Ninja / Visual Studio:同一份 CMakeLists 可生成任一种;VS 是"多配置"生成器(Debug/Release 在构建时选,-DCMAKE_BUILD_TYPE 不适用)

④ 通俗解释【补充】

  • CMake 是"菜单翻译":你写一张中文菜单(CMakeLists.txt),翻译官(cmake)照着客人国籍生成法语/英语/日语菜单(Makefiles/Ninja/VS 工程),厨房(编译器)只看得懂自己的语言;
  • Makefile 是手写的"后厨操作手册":换个厨房要重写;CMake 是"点菜系统":菜单不变,翻译成哪个厨房的操作手册由生成器决定;
  • target 级命令是"按桌配餐":target_include_directories 只给这桌(目标)上佐料,别的桌不受影响——旧式全局命令像"全店泼佐料",一桌口味变了全店遭殃;
  • PRIVATE/PUBLIC/INTERFACE 是"秘方是否外传":PRIVATE=自己后厨用;PUBLIC=自己用也写进菜单给客人看;INTERFACE=自己不炒菜、纯卖秘方(头文件库)。

⑤ 示例代码

代码示例与验证记录

  • examples/ex1-minimal/CMakeLists.txt最小项目(cmake_minimum_required/project/add_executable + out-of-source 构建)✓ 已实测(cmake 4.2.3 / MinGW Makefiles / gcc 15.2.0 / Windows 11, 2026-08-15
    编译:在 ex1-minimal 目录内:cmake -S . -B build -G "MinGW Makefiles" && cmake --build build
    适用环境:LinuxWindows(MinGW)macOS
    展开预期输出(实测)
    -- Configuring done (1.6s)
    -- Generating done (0.0s)
    -- Build files have been written to: <目录>/build
    [ 50%] Building C object CMakeFiles/app.dir/src/main.c.obj
    [100%] Linking C executable app.exe
    [100%] Built target app
    cmake demo: 3+4=7
    [100%] Built target app          (第二遍 cmake --build:无重编译,增量生效)
    

    差异说明:配置耗时随机器不同;第二遍构建只显示 Built target(CMake 生成的 Makefile 同样按时间戳做增量编译)

    完整源码见 /code 代码示例页

  • examples/ex2-multidir/CMakeLists.txt多目录工程(add_subdirectory + add_library + PUBLIC 头文件传播 + PRIVATE 链接)✓ 已实测(cmake 4.2.3 / MinGW Makefiles / gcc 15.2.0 / Windows 11, 2026-08-15
    编译:在 ex2-multidir 目录内:cmake -S . -B build -G "MinGW Makefiles" && cmake --build build
    适用环境:LinuxWindows(MinGW)macOS
    展开预期输出(实测)
    -- Configuring done (1.5s)
    -- Build files have been written to: <目录>/build
    [ 25%] Building C object lib/CMakeFiles/mymath.dir/mymath.c.obj
    [ 50%] Linking C static library libmymath.a
    [ 75%] Building C object CMakeFiles/app.dir/src/main.c.obj
    [100%] Linking C executable app.exe
    [100%] Built target app
    square(7) = 49
    

    差异说明:注意 app 源码里直接

    完整源码见 /code 代码示例页

  • examples/ex3-cmake-diagnostics.txt配置与链接错误诊断(diagnostic:漏 target_link_libraries 的两种真实报错)诊断示例(预期报错/警告)✓ 已实测(cmake 4.2.3 / MinGW Makefiles / gcc 15.2.0 / Windows 11, 2026-08-15
    编译:见文件内说明(实验基于 ex2-multidir 修改后真实执行)
    适用环境:LinuxWindows(MinGW)macOS
    展开预期输出(实测)
    —— 诊断 A(漏 target_link_libraries,编译期)——
    src/main.c:2:10: fatal error: mymath.h: No such file or directory
    mingw32-make[2]: *** [CMakeFiles\app.dir\build.make:78: ...main.c.obj] Error 1
    —— 诊断 B(include 已给但库未链接,链接期)——
    .../ld.exe: CMakeFiles\app.dir/objects.a(main.c.obj):main.c:(.text+0x13): undefined reference to `square'
    collect2.exe: error: ld returned 1 exit status
    

    差异说明:两条均为 cmake 4.2.3 实测真实文本(路径与行号随环境不同);诊断文本在不同编译器/生成器下可能措辞不同,稳定核心是 No such file or directory 与 undefined reference 两行

    完整源码见 /code 代码示例页

⑥ 编译与运行方法

本章命令为 CMake 命令(本机实测:cmake 4.2.3 + MinGW Makefiles 生成器 + gcc 15.2.0 / Windows 11):

cmake -S . -B build -G "MinGW Makefiles"   # 配置+生成(Windows/MinGW;Linux 可省略 -G)
cmake --build build                        # 构建
build\app.exe                              # 运行(Linux: ./build/app)
  • ex1/ex2 的真实输出(配置完成、[50%]/[100%] 构建进度、运行结果、第二遍构建的增量行为)见示例卡折叠区;
  • 多目录 ex2 的关键点是 add_subdirectory(lib) 后,lib 目标通过 target_link_libraries(app PRIVATE mymath) 自动获得 mymath 的 PUBLIC 头文件路径;
  • Debug/Release:cmake -S . -B build -DCMAKE_BUILD_TYPE=Release(注意前缀是 CMAKE_,见勘误 err-7.1-23);多配置生成器(VS)改为构建时选配置。

⑦ 常见错误

症状原因解决
【纠错】写了 -DMAKE_INSTALL_PREFIX / -DMAKE_BUILD_TYPE前缀少字母(资料总结处笔误)写全 -DCMAKE_INSTALL_PREFIX / -DCMAKE_BUILD_TYPE;cmake 对未知变量静默忽略(不报错但不生效),极难排查
fatal error: mymath.h: No such file or directory漏了 target_link_libraries:库的 PUBLIC 头文件路径只随"链接关系"传播target_link_libraries(app PRIVATE mymath)(本机实测诊断 A)
undefined reference to 'square'头文件找得到但没链接库(或链接顺序/库名错)检查 target_link_libraries 与库目标名(本机实测诊断 B)
改了 CMakeLists 不生效直接调 make 而不是 cmake --build;或没重新配置重新 cmake -S . -B build 再构建;统一用 cmake --build
build 目录里到处是产物、源码目录被污染用了 in-source 构建(cmake .)一律 -S 源 -B build out-of-source 构建
cmake --build 提示找不到生成器输出build 目录不是本机生成器生成的用同一生成器重新配置(如 MinGW Makefiles)

⑧ 小练习

小练习

学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)
  • ex-1-1-5-1.cmake -S . -B build 这个命令完成的是哪两个阶段?(单选)

    ◌ 未作答
  • ex-1-1-5-2.mymath 库的头文件路径想让链接它的目标自动获得,应该用哪种传播方式?(单选)

    ◌ 未作答
  • ex-1-1-5-3.构建时报 undefined reference to 'square',但头文件能找到,最可能的原因是?(单选)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

为多文件项目同时提供 CMake 与 Makefile 两套构建

把自己的多文件小项目(如 1.1.4 的 ex1 双文件工程,或自建含一个静态库的三文件工程)改造成 CMake 构建:顶层 CMakeLists + add_subdirectory 子目录,使用 target 级命令与 PRIVATE/PUBLIC, 完成 out-of-source 构建并跑通;再与已有的 Makefile 构建对比说明各自优劣。

输入与输出

无程序输入输出。交付物:① CMakeLists.txt(顶层+子目录);② cmake --build 构建与运行成功记录; ③ 一段"CMake vs Makefile 各自优劣"的 100 字以上小结。

功能要求

  • 工程至少含一个静态库目标(add_library)与一个可执行目标(add_executable)
  • 使用 target_include_directories / target_link_libraries(不用全局旧式命令)
  • 使用 out-of-source 构建(cmake -S . -B build)
  • 分别以 Debug 与 Release 各构建一次并运行(-DCMAKE_BUILD_TYPE,前缀写全 CMAKE_)
  • 写出 CMake 与 Makefile 的优劣对比小结

限制条件

  • 实验在专用目录进行,不污染课程仓库
  • 版本使用当前稳定版 cmake,不锁定陈旧版本

验收步骤(自检清单 0/5

验收标准

  • cmake --build build 无错误且程序输出正确(验收步骤 3)
  • 两种构建类型均构建成功(验收步骤 4)
  • 小结提到"描述 vs 规则"与"生成器/跨平台"两个要点(验收步骤 5)

常见失败原因

  • 漏 target_link_libraries → undefined reference(诊断 B)
  • 头文件找不到 → PUBLIC 传播缺失(诊断 A)
  • 用 in-source 构建污染了源码目录
  • -D 前缀拼成 MAKE_ 导致类型不生效且无报错(err-7.1-23)

可选扩展

  • 用 Ninja 生成器构建一次并对比构建速度(cmake -G Ninja)
  • 给库目标加 INTERFACE 头文件库做一次纯头文件模块实验

完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)

⑪ 面试问题

面试问题

  • CMake 和 Makefile 是什么关系?各自解决什么问题?高频C/C++ · medium

    要点:Makefile 直接描述"怎么编译";CMake 用 CMakeLists.txt 描述"工程长什么样",再生成 Makefile/Ninja/VS 工程等构建脚本。

    Makefile 是构建规则(目标/依赖/命令),与平台和工具强绑定——Windows 的 nmake、Linux 的 make、 生成器的语法都不同。CMake 是更高一层的工程描述:CMakeLists.txt 声明目标、源文件、依赖与使用要求, 由 cmake 按生成器(-G)产出对应构建脚本(Unix Makefiles/MinGW Makefiles/Ninja/Visual Studio)。 分层收益:换平台/换生成器不改工程描述;同一份 CMakeLists 可同时维护 Makefile 与 VS 工程两种构建。

    追问:
    • 追问:既然 CMake 生成 Makefile,为什么不直接用 Makefile?(跨平台描述与生成器抽象、自动依赖分析、模块与测试生态)
    评分要点:
    • 描述 vs 规则的层次关系
    • 生成器抽象与跨平台
  • target_link_libraries 里的 PRIVATE、PUBLIC、INTERFACE 分别是什么意思?高频C/C++ · medium

    要点:控制"使用要求"的传播方向:PRIVATE 只自己用;PUBLIC 自己用且传给下游;INTERFACE 只传给下游(自己不使用)。

    现代 CMake 的依赖围绕 target 展开:一个库的"使用要求"包括头文件路径、编译宏、链接的库。 PRIVATE:这些要求只影响本目标编译(内部实现细节,下游不可见);PUBLIC:本目标编译需要, 且任何链接本目标的下游也自动获得(如头文件路径随链接关系传播);INTERFACE:本目标不产生 编译单元(纯头文件库/仅传递配置),要求只给下游。举例:mymath 的头文件路径必须 PUBLIC, 否则 app 链接 mymath 后依然 #include 不到 mymath.h。

    追问:
    • 追问:为什么现代 CMake 推荐 target 级命令而不用全局 include_directories?(避免目录级污染、依赖显式化)
    评分要点:
    • 三种传播方向定义准确
    • 能举头文件路径的例子
  • 一个工程如何在同一个源码目录下同时维护 Debug 与 Release 两个构建?C/C++ · medium

    要点:out-of-source 构建:同一份源码分别 cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug 与 -B build-release -DCMAKE_BUILD_TYPE=Release,两套产物互不干扰。

    out-of-source 构建把构建产物与源码分离,因此同一源码可以生成任意多个构建目录,每个目录一份 独立的 CMake 缓存与产物。单配置生成器(Makefiles/Ninja)通过配置时的 -DCMAKE_BUILD_TYPE 区分; 多配置生成器(Visual Studio/Xcode)则在构建或打开 IDE 时选择配置。切回 in-source 会污染源码目录 且无法共存多配置——这是社区默认 out-of-source 的原因。

    追问:
    • 追问:多配置生成器下 -DCMAKE_BUILD_TYPE 还有效吗?(无效,构建时选配置;可用 CMAKE_CONFIGURATION_TYPES 控制可选集合)
    评分要点:
    • out-of-source 与多构建目录
    • 单/多配置生成器差异

⑫ 延伸阅读

  • 《Professional CMake》(书名,只引名称:target 级命令与 PRIVATE/PUBLIC/INTERFACE 的权威讲解);
  • CMake 官方文档:cmake.org/documentation(cmake-buildsystem(7) 手册讲 target 与使用要求);
  • 本地手册:cmake --help / cmake --help-command target_link_libraries
  • 下一章预告:1.1.6 GDB 调试实战——构建出 Debug 版程序后,用 GDB 定位崩溃。

迁移训练(migration training)

把本章技能迁移到 LinuxmacOS

环节Windows(MinGW)Linux / macOS
生成器MinGW Makefiles(本机实测)Unix Makefiles(默认)/ Ninja
命令一致(cmake -S . -B build && cmake --build build)一致
产物app.exebuild/app(无后缀)
多配置不适用(单配置生成器)不适用;VS/Xcode 多配置构建时选
交叉编译不涉及-DCMAKE_TOOLCHAIN_FILE=xxx.cmake(7.2 展开)

不变的:CMakeLists.txt 内容、target 级命令、三阶段、PRIVATE/PUBLIC/INTERFACE 语义;要改的:生成器名与产物后缀(平台差异由 cmake 吸收,这正是它的价值)。

内容来源映射

内容部分资料位置标记说明
配置/生成/构建三阶段、CMakeLists 基本结构与命令、多目录 add_subdirectory、Debug/Release 配置第一阶段讲义5.x 节来源正文在原资料基础上重新组织表述,未大段复制原文
-D 变量拼写(总结处 -DMAKE_* 笔误)第一阶段讲义5.x 节(PAGE 143)纠错见勘误 err-7.1-23;正文一律使用 -DCMAKE_ 前缀
原资料 CMake 示例第一阶段讲义5.x 节待确认全部示例以截图嵌入、无法还原 → 本章示例代码全部重写并本机实测(cmake 4.2.3)
现代 target 级写法(target_include_directories/target_link_libraries 与 PRIVATE/PUBLIC/INTERFACE)、生成器差异、实验记录补充原资料以全局 include_directories 等旧式写法为主;本章按现代推荐重构,比例约 40%
小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读补充原资料该章无成体系练习,全部新编

本章勘误与更新记录(【纠错】/【更新】)

  • err-7.1-23 · 技术错误 · 出处 PAGE 143

    原文:资料总结处把 -D 变量写成 -DMAKE_INSTALL_PREFIX / -DMAKE_BUILD_TYPE

    正确:正确拼写为 -DCMAKE_INSTALL_PREFIX / -DCMAKE_BUILD_TYPE(前缀是 CMAKE_ 不是 MAKE_;资料正文页写法正确,总结处笔误)

    原因:第一步报告 7.1 第 23 条:少字母会导致 cmake 生成时静默忽略该变量(不报错但不生效),是危险的笔误