6. CMake入门:CMakeLists.txt基本语法、project()与add_executable()、设置C标准
说实话,很多C语言初学者甚至工作两三年的工程师,对CMake的态度就是「能跑就行」。我以前带团队时,经常看到有人把CMakeLists.txt写得跟流水账一样,编译选项乱堆,路径硬编码,换台机器就崩。嗯,这其实是个大坑。
我个人习惯是,从第一个工程开始就把CMakeLists.txt写得规规矩矩。别觉得麻烦,后面省下的时间绝对值得。今天我们就从最基础的CMake语法讲起,把project()、add_executable()和C标准设置这几个核心点彻底说清楚。
6.1 CMakeLists.txt 长什么样?
说白了,CMakeLists.txt就是CMake的构建脚本。它告诉CMake:你的源码在哪、要生成什么目标、用哪个编译器、链接什么库。每个CMake项目,根目录下必须有一个CMakeLists.txt。
一个最简单的CMakeLists.txt,大概长这样:
cmake_minimum_required(VERSION 3.10)
project(HelloWorld)
add_executable(hello main.c)
三行代码,搞定一个可执行文件。我在项目中见过有人连cmake_minimum_required都不写,结果换到新版本CMake直接报错。所以,第一行一定要写,指定最低版本要求。
6.2 project() —— 给你的项目起个名
project() 是CMake的入口函数。它不光定义项目名称,还会自动设置一些变量,比如PROJECT_NAME、PROJECT_SOURCE_DIR、PROJECT_BINARY_DIR。这些变量后面很有用。
基本语法:
project(<项目名> [语言列表])
举个例子:
project(MyApp C CXX)
这表示项目名叫MyApp,支持C和C++。如果你只写C,那就只检测C编译器。我个人习惯是明确指定语言,避免CMake去额外检测不需要的编译器,能省一点配置时间。
我曾经遇到一个同事,把project()写在了add_executable()后面,结果折腾了半天找不到原因。嗯,这种低级错误,犯过一次就记住了。
6.3 add_executable() —— 生成可执行文件
add_executable() 就是告诉CMake:我要生成一个可执行文件,源码是哪些。语法很简单:
add_executable(<目标名> [源文件列表])
比如:
add_executable(myapp main.c utils.c)
这里的目标名myapp就是最终生成的可执行文件名(Windows下是myapp.exe)。源文件可以写多个,用空格隔开。
你可能会问:如果源文件很多,一个个写岂不累死?没错,所以CMake提供了aux_source_directory()和file(GLOB ...)来自动收集源文件。但我建议你谨慎使用file(GLOB),因为它不会自动检测新增文件,除非你重新运行cmake。我在项目中吃过这个亏,加了个新文件忘了重新cmake,链接报错找了半天。
6.4 设置C标准 —— 让编译器按规矩来
C语言标准有很多:C89、C99、C11、C17、C23。不同编译器默认标准不一样,比如GCC 10.x默认是C17,而有些老版本GCC默认是C11。如果你用了C11的特性,但编译器默认是C99,那就编译不过。
所以,显式设置C标准是专业做法。CMake提供了两种方式:
方式一:设置全局变量
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
CMAKE_C_STANDARD:指定C标准版本,比如11、17、23。CMAKE_C_STANDARD_REQUIRED:设为ON表示强制使用该标准,如果编译器不支持就报错。CMAKE_C_EXTENSIONS:设为OFF表示禁用编译器扩展(比如GNU扩展),只使用标准C。
方式二:针对单个目标设置
set_target_properties(myapp PROPERTIES
C_STANDARD 11
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)
我个人习惯用全局设置,因为一个项目通常统一用一种标准。但如果你有多个目标需要不同标准,那就用set_target_properties。
6.5 一个完整的示例
把上面这些串起来,写一个完整的CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
# 项目名称,支持C语言
project(Calculator C)
# 设置C标准为C11,强制使用,禁用扩展
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
# 收集源文件(手动列出,更可靠)
set(SOURCES
main.c
add.c
subtract.c
multiply.c
)
# 生成可执行文件
add_executable(calc ${SOURCES})
这个例子中,我手动列出了源文件,而不是用GLOB。为什么?因为手动列出更清晰,别人看CMakeLists.txt就知道项目有哪些源文件。而且,用set(SOURCES ...)的方式,后面加文件只需要改一处,维护起来很方便。
6.6 知识体系总览
下面这张图,把CMake入门阶段的核心知识点串起来了。你可以看到,从CMakeLists.txt出发,经过project()、add_executable()、C标准设置,最终生成可执行文件。每一步都有对应的变量和函数。
6.7 常见问题与避坑
- 问题:CMake报错“找不到源文件”。
原因:路径写错了,或者源文件不在CMakeLists.txt所在目录。我建议所有源文件放在项目根目录下的src/文件夹里,然后在CMakeLists.txt中用${PROJECT_SOURCE_DIR}/src/拼接路径。 - 问题:设置了C标准,但编译器还是用了别的标准。
原因:忘记写CMAKE_C_STANDARD_REQUIRED ON。如果不写,CMake只会尝试使用指定标准,如果编译器不支持就悄悄降级。我曾经因为这个原因,代码里用了C11的_Generic,结果在某个老编译器上编译通过但行为异常,排查了很久才发现是标准被降级了。 - 问题:add_executable()之后,想改目标名怎么办?
答案:不能直接改,只能删掉重新add。所以命名要慎重,我一般用项目名小写加下划线,比如my_app。
6.8 总结
CMake入门其实不复杂。记住三件事:project()起名字、add_executable()生成目标、set(CMAKE_C_STANDARD ...)定标准。把这三板斧练熟,大部分单文件或多文件的小项目都能搞定。
嗯,下一节我们会深入CMake的变量、条件判断和循环,到时候你会发现,CMake其实也是一门「小语言」,写好了比手写Makefile舒服得多。