
CMake 是一个跨平台的构建工具生成器,它不是直接编译代码,而是根据我们编写的 CMakeLists.txt 配置文件,自动生成对应平台的编译构建文件:
Makefile 文件,配合 make 命令编译.sln 工程文件、MinGW 的 Makefile核心优势:一次编写 CMakeLists.txt,所有平台通用,彻底解决跨平台编译的适配问题,是C/C++项目的标准构建方案。
C语言项目用CMake构建的标准4步流程(固定不变,牢记),后续所有项目都用这套流程:
# 步骤1:创建编译目录(推荐,源码与编译产物分离,叫build最规范)
mkdir build && cd build
# 步骤2:执行cmake命令,解析上级目录的CMakeLists.txt,生成Makefile
cmake ..
# 步骤3:执行make命令,读取Makefile编译源码,生成可执行文件/库文件
make
# 步骤4:运行编译好的可执行程序(Linux/Mac),Windows是.exe文件
./可执行程序名
确保你的系统已安装 CMake 和 Make(Linux 一般自带,Windows 推荐用 MinGW/msys2),终端执行如下命令有版本输出即正常:

cmake --version # 验证cmake安装
make --version # 验证make安装
CMake 的配置文件必须命名为 CMakeLists.txt(大小写敏感!不能改名字),所有编译规则都写在这个文件里,下面是C语言项目开发中100%会用到的核心语法,全是高频必用,掌握后即可应对99%的场景:
cmake_minimum_required 和 CMAKE_MINIMUM_REQUIRED 等效,推荐小写; 也可以不加,CMake会自动识别,推荐不加,更简洁# 开头(和Shell、Python一致),无多行注释${变量名}cmake_minimum_required(VERSION 3.10)
作用:声明当前CMakeLists.txt需要的CMake最低版本,防止版本过低导致语法不兼容,建议写3.10及以上(主流版本)。
project(项目名称)
# 示例:project(MyCProject)
作用:定义项目名称,CMake会自动生成相关变量,同时会创建默认的编译环境,无需额外配置C语言编译器(CMake会自动识别gcc/clang)。
set(CMAKE_C_STANDARD 99) # 指定C99标准,也可以写11(C11)、17(C17)
set(CMAKE_C_STANDARD_REQUIRED ON) # 强制要求指定的C标准,不兼容则报错
作用:指定编译C语言代码的C标准(比如C99支持变长数组、for循环初始化等特性),不加会使用编译器默认标准(可能是C89,特性受限),这是C语言项目的必配项!
# 写法1:单文件编译,语法:add_executable(可执行程序名 源文件.c)
add_executable(main main.c)
# 写法2:多文件编译,语法:add_executable(可执行程序名 源文件1.c 源文件2.c 源文件3.c)
add_executable(myapp a.c b.c c.c)
核心作用:告诉CMake,将指定的C源文件编译链接,生成一个可执行程序,程序名自定义(比如main、myapp)。
# 语法:aux_source_directory(目录路径 变量名)
aux_source_directory(. SRC_LIST) # 匹配【当前目录】下的所有.c源文件,存入变量SRC_LIST
作用:当源文件很多时,不用手动写 a.c b.c c.c,CMake会自动扫描指定目录下所有.c文件,存入变量,后续直接用变量即可,示例:
aux_source_directory(. SRC_LIST)
add_executable(myapp ${SRC_LIST}) # ${变量名} 是CMake读取变量的固定语法
# 生成静态库(Linux后缀.a,Windows后缀.lib),语法:add_library(库名 STATIC 源文件.c)
add_library(mylib STATIC calc.c log.c)
# 生成动态库(Linux后缀.so,Windows后缀.dll),语法:add_library(库名 SHARED 源文件.c)
add_library(mylib SHARED calc.c log.c)
作用:将公共的C代码(比如工具函数、业务模块)编译成库文件,供主程序调用,实现代码复用和解耦,这是大型C项目的核心写法。
# 语法:target_link_libraries(可执行程序名 库名)
target_link_libraries(main mylib)
作用:告诉编译器,编译主程序时链接指定的库文件(静态库/动态库),这样主程序就能调用库里的函数了,必须和add_library配合使用。
# 语法:include_directories(头文件目录1 目录2 ...)
include_directories(./include ./src)
作用:当C源文件中用#include "xxx.h"或#include <xxx.h>引入头文件时,如果头文件不在当前目录,CMake编译会找不到头文件报错,这个命令就是添加头文件的搜索路径,编译器会自动在这些目录里找头文件。
hello_c/ # 项目根目录
├── main.c # 唯一的C源文件
└── CMakeLists.txt# CMake配置文件
#include <stdio.h>
int main() {
int arr[] = {1,2,3,4,5};
// 这里用了C99的for循环初始化特性,必须配置CMAKE_C_STANDARD 99
for(int i=0; i<5; i++){
printf("Hello CMake! i = %d, arr[i] = %d\n", i, arr[i]);
}
return 0;
}
# 1. 指定CMake最低版本
cmake_minimum_required(VERSION 3.10)
# 2. 定义项目名称
project(SingleFileC)
# 3. 指定C99标准,强制要求
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 4. 编译生成可执行程序:程序名single_main,源文件main.c
add_executable(single_main main.c)
cd hello_c # 进入项目根目录
mkdir build && cd build # 创建编译目录并进入
cmake .. # 生成Makefile
make # 编译代码
./single_main # 运行程序(Windows是single_main.exe)
运行结果:输出5行Hello CMake!,无报错即成功。
项目里有多个.c文件和对应的.h头文件,所有文件都在同一个目录下,比如:计算模块(calc.c/calc.h)+ 日志模块(log.c/log.h)+ 主函数(main.c),这是C项目最常见的结构。
multi_c/
├── main.c # 主函数,调用calc和log的函数
├── calc.c # 计算模块:加减乘除函数
├── calc.h # 计算模块头文件:函数声明
├── log.c # 日志模块:打印日志函数
├── log.h # 日志模块头文件:函数声明
└── CMakeLists.txt
calc.h
#ifndef CALC_H
#define CALC_H
int add(int a, int b); // 加法声明
int mul(int a, int b); // 乘法声明
#endif
calc.c
#include "calc.h"
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
log.h
#ifndef LOG_H
#define LOG_H
void print_log(const char *msg); // 日志打印声明
#endif
log.c
#include "log.h"
#include <stdio.h>
void print_log(const char *msg) {
printf("[LOG] %s\n", msg);
}
main.c
#include <stdio.h>
#include "calc.h"
#include "log.h"
int main() {
print_log("程序启动");
int a=10, b=5;
printf("a + b = %d\n", add(a,b));
printf("a * b = %d\n", mul(a,b));
print_log("程序结束");
return 0;
}
cmake_minimum_required(VERSION 3.10)
project(MultiFileC)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 直接罗列所有.c文件,生成可执行程序multi_main
add_executable(multi_main main.c calc.c log.c)
cmake_minimum_required(VERSION 3.10)
project(MultiFileC)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 自动扫描当前目录所有.c文件,存入SRC_LIST变量
aux_source_directory(. SRC_LIST)
# 用变量编译,无需手动罗列
add_executable(multi_main ${SRC_LIST})
还是固定4步,执行后会生成multi_main可执行程序,运行结果如下:
[LOG] 程序启动
a + b = 15
a * b = 50
[LOG] 程序结束
这是大型C语言项目的标准结构:源码与头文件分离、模块与主函数分离,所有公共的模块代码放在src/目录,所有头文件放在include/目录,主函数放在根目录或单独的main/目录,优点是:代码结构清晰、模块化强、易于维护。
pro_c/ # 项目根目录
├── main.c # 项目主函数(入口文件)
├── include/ # 所有头文件存放目录(公共头文件)
│ ├── calc.h
│ └── log.h
├── src/ # 所有模块源码存放目录(公共实现)
│ ├── calc.c
│ └── log.c
└── CMakeLists.txt # 根目录的CMake配置文件(核心)
所有
.h在include,所有模块.c在src,主函数在根目录,这是C项目的黄金结构!
和「实战二」的calc.c/calc.h/log.c/log.h/main.c完全一致,只是文件位置变了,这里不再重复粘贴。
这个场景的核心是:CMake找不到include的头文件、找不到src的源文件,所以必须加两个关键配置:include_directories(添加头文件路径)+ aux_source_directory(扫描src目录的源文件)
cmake_minimum_required(VERSION 3.10)
project(ProC)
# 指定C99标准
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# ========== 多目录核心配置1 ==========
# 添加头文件搜索路径:告诉编译器去include目录找头文件
include_directories(./include)
# ========== 多目录核心配置2 ==========
# 扫描src目录下的所有.c源文件,存入SRC变量
aux_source_directory(./src SRC)
# ========== 编译可执行程序 ==========
# 源文件包含:根目录的main.c + src目录的所有.c文件(${SRC})
add_executable(pro_main main.c ${SRC})
依旧是固定4步,无任何变化,运行结果和实战二一致,成功!
当项目越来越大,比如有10个以上模块,我们不希望把所有源码都编译到主程序里,而是把公共模块编译成库文件(静态库/动态库),主程序只需要「链接库文件」即可调用函数,这是解耦、复用、分模块开发的核心方案,也是企业级C项目的标配!
lib_c/
├── main.c # 主程序:只调用库函数,不关心实现
├── include/ # 头文件目录
│ ├── calc.h
│ └── log.h
├── src/ # 模块源码:编译成库文件
│ ├── calc.c
│ └── log.c
└── CMakeLists.txt
src/下的calc.c + log.c编译成静态库(比如叫utils)main.c编译时,链接这个静态库include的头文件声明,调用库里的函数cmake_minimum_required(VERSION 3.10)
project(LibC)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# 1. 添加头文件搜索路径
include_directories(./include)
# 2. 扫描src目录的源文件
aux_source_directory(./src SRC)
# 3. 核心:将src的源码编译成【静态库】,库名utils
add_library(utils STATIC ${SRC})
# 4. 核心:编译主程序,然后【链接】静态库utils
add_executable(lib_main main.c)
target_link_libraries(lib_main utils)
执行4步流程后,会发现build目录下多了一个libutils.a文件(Linux静态库),这就是我们编译的库文件,运行./lib_main,结果和之前一致,成功!
替换成动态库:只需要把
STATIC改成SHARED,即add_library(utils SHARED ${SRC}),会生成libutils.so动态库,其他配置不变。
编译后如果想重新编译、或者修改了CMakeLists.txt,需要清理编译产物,执行如下命令(在build目录下):
make clean # 清理编译生成的可执行程序、库文件等(保留CMake生成的Makefile)
rm -rf * # 彻底清空build目录(Linux/Mac),Windows用 del *
建议:修改CMakeLists.txt后,最好清空build目录重新执行cmake+make,避免缓存导致的编译错误。
在CMakeLists.txt中添加如下配置,可以指定编译器的优化级别,提升程序运行效率,推荐添加:
# 可选级别:-O0(无优化,调试用)、-O1(基础优化)、-O2(最优,推荐)、-O3(极致优化)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
如果需要用gdb调试C程序,必须在编译时添加调试信息,CMake配置如下:
set(CMAKE_BUILD_TYPE Debug) # Debug模式,生成调试信息;Release模式无调试信息
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g") # 强制添加-g调试参数
添加后,编译的程序可以直接用gdb ./程序名调试。
CMake会自动识别系统默认的C编译器(Linux是gcc,Mac是clang),如果想手动指定编译器,在执行cmake时添加参数即可:
cmake .. -DCMAKE_C_COMPILER=gcc # 指定gcc为C编译器
cmake .. -DCMAKE_C_COMPILER=clang # 指定clang为C编译器
新手用CMake最容易遇到的几个错误,全部整理好了,遇到直接查即可:
fatal error: xxx.h: No such file or directoryinclude_directories(头文件所在目录)undefined reference to 'xxx'.c文件加入了add_executabletarget_link_libraries链接了库文件error: ‘for’ loop initial declarations are only allowed in C99 modeset(CMAKE_C_STANDARD 99) 和 set(CMAKE_C_STANDARD_REQUIRED ON)CMake Error: The source directory "xxx" does not appear to contain CMakeLists.txtcmake ..时,上级目录没有CMakeLists.txt,或者cd到了错误的目录build目录下执行cmake ..mkdir build && cd build → cmake .. → make → ./程序名
cmake_minimum_required(VERSION 3.10):指定CMake版本project(xxx):项目名set(CMAKE_C_STANDARD 99):指定C标准,必加!include_directories(路径):添加头文件搜索路径aux_source_directory(路径 变量):批量匹配.c文件add_executable(程序名 源文件):生成可执行程序add_library(库名 STATIC/SHARED 源文件):生成库文件target_link_libraries(程序名 库名):链接库文件根目录/main.c + include/头文件 + src/源文件(企业级标准)