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直接报错。所以,第一行一定要写,指定最低版本要求。

小提示:cmake_minimum_required 建议写你实际测试过的版本。比如你本地是3.16,可以写3.10,但别写2.8,太老了,很多新特性不支持。

6.2 project() —— 给你的项目起个名

project() 是CMake的入口函数。它不光定义项目名称,还会自动设置一些变量,比如PROJECT_NAMEPROJECT_SOURCE_DIRPROJECT_BINARY_DIR。这些变量后面很有用。

基本语法:

project(<项目名> [语言列表])

举个例子:

project(MyApp C CXX)

这表示项目名叫MyApp,支持C和C++。如果你只写C,那就只检测C编译器。我个人习惯是明确指定语言,避免CMake去额外检测不需要的编译器,能省一点配置时间。

重点:project() 必须在 add_executable() 之前调用。这是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,链接报错找了半天。

避坑指南:add_executable() 的目标名不要和项目名重复,否则容易混淆。另外,目标名中不要有空格或特殊字符,用下划线或驼峰命名就好。

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

经验之谈:嵌入式项目我建议用C11或C17,因为C99太老,很多新特性不支持;C23又太新,编译器支持不完整。C11是当前最稳妥的选择。

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标准设置,最终生成可执行文件。每一步都有对应的变量和函数。

CMake入门知识体系 CMakeLists.txt project() 项目名称 语言列表 add_executable() 目标名 源文件列表 设置C标准 (CMAKE_C_STANDARD) 核心文件 项目配置 构建目标 标准控制

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舒服得多。


公众号:蓝海资料掘金营,微信deep3321