CMake入门——简介和基本使用
CMake 简介与使用方法
1. cmake 简介
什么是 cmake?
cmake 是一个跨平台的自动构建工具,它的诞生主要是为了解决直接使用 make + Makefile 方式无法实现跨平台的问题。
cmake 的主要优点
| 优点 | 说明 |
|---|---|
| 开放源代码 | 可从 cmake 官网 https://cmake.org/ 下载源代码 |
| 跨平台 | cmake 不直接编译出最终文件,而是允许开发者编写与平台无关的 CMakeLists.txt,cmake 解析后根据当前编译平台生成本地化的 Makefile,最后由 make 编译 |
| 语法规则简单 | Makefile 语法复杂且不同平台不统一;cmake 的 CMakeLists.txt 语法与平台无关,简单易懂 |
cmake 的工作流程

本质:cmake 只是一个"生成器",真正执行编译的还是
make工具。
2. cmake 和 Makefile
- Makefile:Linux 下传统的构建脚本,直接由
make工具解析执行。不同平台需要编写不同的 Makefile,跨平台能力弱。 - cmake:自动生成 Makefile 的工具。开发者只需编写一份平台无关的
CMakeLists.txt,cmake 会根据当前平台自动生成对应的 Makefile。
除了 cmake 之外,还有 automake、autoconf 等,但 cmake 是目前最主流的选择。
3. cmake 的使用方法
3.1 安装 cmake
在 Ubuntu 系统中,可以通过 apt-get 在线安装:
sudo apt-get install cmake
安装完成后查看版本号:
cmake --version
3.2 官方学习资源
- 文档总链接:https://cmake.org/documentation/
- 官方培训教程:https://cmake.org/cmake/help/latest/guide/tutorial/index.html
示例一:单个源文件
工程结构:
├── CMakeLists.txt
└── main.c
main.c:
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
CMakeLists.txt:
project(HELLO)
add_executable(hello ./main.c)
构建步骤:
cmake ./ # 生成 Makefile
make # 编译得到可执行文件 hello
./hello # 运行
CMakeLists.txt 说明:
CMakeLists.txt 中的命令用法类似于 C 语言函数——命令名后跟一对括号,多个参数之间用空格分隔,而不是逗号。
-
project(HELLO)—— project 命令用于设置工程的名称。括号中的HELLO就是要设置的工程名。虽然设不设置工程名不是强制性的,但最好加上。设置工程名后,cmake 会自动定义两个变量:PROJECT_SOURCE_DIR(源码路径)和PROJECT_BINARY_DIR(构建路径),后续会用到。 -
add_executable(hello ./main.c)—— add_executable 命令用于生成可执行文件。第一个参数hello指定生成的可执行文件名;第二个参数./main.c指定源文件路径。cmake 解析此命令后,会在生成的 Makefile 中写入对应的编译规则,最终make编译出来就是一个名为hello的可执行文件。
out-of-source 构建(推荐)
为了避免 cmake 生成的中间文件与源代码混在一起,推荐使用 out-of-source 方式构建:
├── build/ # 新建 build 目录
├── CMakeLists.txt
└── main.c
cd build/
cmake ../ # 在 build 目录中运行 cmake
make # 编译
为什么是 cmake ../?
cmake 命令的参数指定的是 CMakeLists.txt 所在路径,而非当前工作目录。
- 此时我们在
build/目录下,而CMakeLists.txt在上层(工程根目录) ../表示上层目录,cmake 会去那里寻找CMakeLists.txt并解析- 生成的
Makefile、CMakeCache.txt等中间文件全部输出到当前目录(即build/),不会污染源码目录 - 如果要清理工程,只需
rm -rf build/即可
所有中间文件和最终产物都在
build/目录下,清理时直接删除build/目录即可。

示例二:多个源文件
工程结构:
├── build/
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c
CMakeLists.txt:
project(HELLO)
set(SRC_LIST main.c hello.c)
add_executable(hello ${SRC_LIST})
关键点:
-
set(SRC_LIST main.c hello.c)—— set 命令用于定义变量(类似于 C 语言的宏定义)。这里定义了一个名为SRC_LIST的变量,其值为源文件列表main.c hello.c。变量名一般用大写,以便与命令名区分。 -
${SRC_LIST}—— 在 cmake 中,${变量名}表示引用变量的值。cmake 预处理时会将${SRC_LIST}展开为main.c hello.c,等价于直接写add_executable(hello main.c hello.c)。你也可以不定义变量,直接列出源文件:add_executable(hello main.c hello.c) -
关于
./main.c与main.c的区别 —— 在示例一中我们写的是./main.c,这里写的是main.c,两者在 cmake 中是完全等价的,都表示当前目录下的main.c文件。./只是显式地标明了"当前目录",加不加没有区别。第一个例子加上./是为了让初学者更清楚地看出路径关系,后续示例为了简洁就去掉了。
示例三:生成库文件
将 hello.c 编译为库文件,并链接到可执行文件。
CMakeLists.txt:
project(HELLO)
add_library(libhello hello.c)
add_executable(hello main.c)
target_link_libraries(hello libhello)
构建结果:
├── build/
│ ├── hello
│ └── liblibhello.a
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c
相关命令详解:
| 命令 | 作用 |
|---|---|
add_library(libhello hello.c) |
生成静态库文件 liblibhello.a(默认生成静态库) |
add_library(libhello SHARED hello.c) |
生成动态库文件(共享库 .so) |
add_library(libhello STATIC hello.c) |
生成静态库文件(显式指定) |
target_link_libraries(hello libhello) |
将 libhello 库链接到 hello 可执行文件 |
什么叫"链接到可执行文件"?
先回顾一下 C/C++ 程序的编译过程:
源文件(.c) → 编译(compile) → 目标文件(.o)
目标文件(.o) + 库文件(.a/.so) → 链接(link) → 可执行文件
编译阶段:每个 .c 文件被单独编译成 .o 目标文件。此时如果 main.c 中调用了 hello() 函数,编译器在 main.o 中只会留下一个"未解析的符号"(函数调用地址待定)。
链接阶段:链接器将各个 .o 文件拼在一起,同时去指定的库文件中查找那些"未解析的符号"。如果找到了,就把库中对应的机器码合并到最终的可执行文件中。
举个例子 —— 本例中:
main.c调用了hello("World")函数- 这个函数定义在
hello.c中,并被编译到了libhello.a(或libhello.so)中 - 如果没有
target_link_libraries(hello libhello),链接器就不知道上哪去找hello函数的实现,会报"未定义的引用"错误 - 加上之后,链接器就会去
libhello库中查找并解析这个符号,最终成功生成可执行文件
静态链接和动态链接:
| 类型 | 命令 | 原理说明 | 优点 | 缺点 |
|---|---|---|---|---|
| 静态库 (.a) | add_library(libhello hello.c) |
链接时,链接器把 libhello.a 中 hello() 函数的机器码原样拷贝一份,塞进 hello 这个可执行文件里。最终 hello 文件本身就包含了 hello() 的全部代码。 |
生成的可执行文件不依赖外部的库文件,拷贝到别的机器上也能直接运行。 | ① 如果 10 个程序都用到了同一个静态库,每个程序都含有一份拷贝,磁盘和内存浪费;② 如果库修复了一个 bug,所有用到它的程序都得重新链接。 |
| 动态库 (.so) | add_library(libhello SHARED hello.c) |
链接时,链接器不拷贝代码,只在 hello 可执行文件中记下一笔:"我需要 libhello.so 中的 hello() 函数"。等到真正运行 ./hello 时,系统才去硬盘上找到 libhello.so,加载到内存,再让 hello 程序去调用它。 |
① 多个程序可以共享内存中的同一份 .so(物理内存只存一份),节省内存;② 单独更新 .so 文件就能修复所有程序的 bug,无需重新链接。 | 运行环境必须存在对应的 .so 文件,否则程序启动会报错 "cannot open shared object file"。 |
修改输出库文件名称:
默认会生成 liblibhello.a(lib + 目标名 libhello + .a),前缀和后缀重复了,如果想生成 libhello.a 就需要修改输出名称:
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
语法解释: set_target_properties(目标名 PROPERTIES 属性名 "属性值")
libhello—— 要修改的目标名,即add_library(libhello hello.c)中定义的那个目标PROPERTIES—— 关键字,告诉 cmake 后面要跟属性-值对OUTPUT_NAME—— 属性名,表示"输出文件的基础名称(不含前缀后缀)""hello"—— 属性值,将输出名称设为hello
Linux 下库文件的命名规则是:
lib+ 基础名称 +.后缀。所以OUTPUT_NAME设为"hello"后,最终生成的文件就是libhello.so(或libhello.a),而不是默认的liblibhello.a。
完整示例:
cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_library(libhello SHARED hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
add_executable(hello main.c)
target_link_libraries(hello libhello)
cmake_minimum_required(VERSION 3.5):设置当前工程的最低 cmake 版本要求,非强制但建议加上。
目标名(target name)在整个工程中必须唯一,不可重复。
示例四:将源文件组织到不同目录
当源文件较多时,应按功能/模块拆分到不同目录。
工程结构:
├── build/
├── CMakeLists.txt # 顶层
├── libhello/
│ ├── CMakeLists.txt # 子目录
│ ├── hello.c
│ └── hello.h
└── src/
├── CMakeLists.txt # 子目录
└── main.c
顶层 CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_subdirectory(libhello)
add_subdirectory(src)
src/CMakeLists.txt:
include_directories(${PROJECT_SOURCE_DIR}/libhello)
add_executable(hello main.c)
target_link_libraries(hello libhello)
libhello/CMakeLists.txt:
add_library(libhello hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
关键命令:
| 命令/变量 | 作用 |
|---|---|
add_subdirectory(libhello) |
告诉 cmake 去子目录寻找 CMakeLists.txt 并解析 |
include_directories(...) |
指定头文件搜索路径 |
${PROJECT_SOURCE_DIR} |
cmake 内置变量,指向工程源码根目录 |
构建结果:
├── build/
│ ├── libhello/
│ │ └── libhello.a
│ └── src/
│ └── hello
├── CMakeLists.txt
├── libhello/
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src/
├── CMakeLists.txt
└── main.c
示例五:分离可执行文件和库文件
在示例四的基础上,让可执行文件输出到 build/bin,库文件输出到 build/lib,不再和 cmake 中间文件混在一起。本例同样是三个 CMakeLists.txt。
构建前目录结构(源码组织):
├── build/ # 空目录,用于 out-of-source 构建
├── CMakeLists.txt # ⬅ 顶层 CMakeLists.txt
├── libhello/ # 库模块目录
│ ├── CMakeLists.txt # ⬅ 子目录 CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src/ # 主程序目录
├── CMakeLists.txt # ⬅ 子目录 CMakeLists.txt
└── main.c
三个 CMakeLists.txt 的内容及职责:
① 顶层 CMakeLists.txt —— 与示例四完全相同:
cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_subdirectory(libhello)
add_subdirectory(src)
② src/CMakeLists.txt —— 与示例四相比,多了一行 EXECUTABLE_OUTPUT_PATH,指定可执行文件的输出目录:
include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(hello main.c)
target_link_libraries(hello libhello)
③ libhello/CMakeLists.txt —— 与示例四相比,多了一行 LIBRARY_OUTPUT_PATH,指定库文件的输出目录:
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
add_library(libhello hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
关键变量:
| 变量 | 作用 |
|---|---|
EXECUTABLE_OUTPUT_PATH |
控制可执行文件的输出路径 |
LIBRARY_OUTPUT_PATH |
控制库文件的输出路径 |
${PROJECT_BINARY_DIR} |
cmake 内置变量,指向构建目录的根目录(即 build 目录) |
变量 谁定义的 值(以本示例结构为例) PROJECT_SOURCE_DIRproject()自动定义顶层 CMakeLists.txt所在目录,即/project/PROJECT_BINARY_DIRproject()自动定义执行 cmake的目录,即/project/build/EXECUTABLE_OUTPUT_PATH需用户手动 set()不设置则默认为 PROJECT_BINARY_DIRLIBRARY_OUTPUT_PATH需用户手动 set()不设置则默认为 PROJECT_BINARY_DIR所以本例中
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)等价于设置输出路径为/project/build/bin。
构建后目录结构(cd build && cmake .. && make 之后):
├── build/ # 所有构建产物都在这里
│ ├── bin/
│ │ └── hello # ⬅ 可执行文件单独放在 bin/
│ ├── lib/
│ │ └── libhello.a # ⬅ 库文件单独放在 lib/
│ ├── CMakeCache.txt # cmake 生成的中间文件
│ ├── CMakeFiles/
│ ├── cmake_install.cmake
│ └── Makefile
├── CMakeLists.txt # 源码不变
├── libhello/
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src/
├── CMakeLists.txt
└── main.c
4. 常用命令速查表
| 命令 | 功能 |
|---|---|
project(name) |
设置工程名称 |
cmake_minimum_required(VERSION x.y) |
设置最低 cmake 版本 |
add_executable(target src) |
生成可执行文件 |
add_library(target src) |
生成库文件(默认静态库) |
add_library(target SHARED src) |
生成动态库 |
add_library(target STATIC src) |
生成静态库 |
target_link_libraries(target lib) |
链接库到目标 |
set(var value) |
设置变量 |
${var} |
引用变量 |
add_subdirectory(dir) |
添加并解析子目录的 CMakeLists.txt |
include_directories(path) |
指定头文件搜索路径 |
set_target_properties(...) |
设置目标属性 |

浙公网安备 33010602011771号