5、变量与缓存:普通变量、缓存变量、环境变量、变量的作用域、set()和unset()命令

说实话,CMake 里的变量系统,是很多新手第一个栽跟头的地方。我刚接触 CMake 那会儿,也搞不清楚「普通变量」和「缓存变量」到底有啥区别,反正都是存个值嘛,能跑就行。结果有一次,我在项目里改了某个变量,重新 cmake 之后发现它还是旧值……嗯,从那以后,我才老老实实把这块啃了一遍。

今天我们就来把这套变量体系捋清楚。你只要记住一句话:普通变量是临时的,缓存变量是持久的,环境变量是系统给的。

5.1 普通变量

普通变量是最常用的。说白了,它就是你在 CMakeLists.txt 里临时定义的一个名字-值对,只在当前作用域有效。

set(MY_VAR "hello")
message("${MY_VAR}")  # 输出 hello

这里我用 set() 命令定义了一个普通变量 MY_VAR,值为 "hello"。注意,变量名习惯用大写,这不是强制要求,但大家都这么写,你也跟着写就行。

普通变量有个特点:它不会跨 CMake 运行保留。你这次执行 cmake 定义了它,下次再执行 cmake,它就没了。所以普通变量适合用来做临时计算、传递参数、控制流程。

小技巧: 我个人习惯把普通变量当作「函数内的局部变量」来理解。你在一个函数里定义的局部变量,外面是看不到的,CMake 的普通变量也是这个道理。

5.2 缓存变量

缓存变量就不一样了。它会被写入 CMakeCache.txt 文件,下次你再运行 cmake,它还在。这玩意儿通常用来保存用户配置,比如安装路径、编译选项、开关标志等。

set(MY_CACHE_VAR "world" CACHE STRING "这是一个缓存变量示例")

注意看,这里多了 CACHE 关键字,后面还跟了类型 STRING 和一段描述。类型可以是 BOOLFILEPATHPATHSTRINGINTERNAL 等。描述信息会在 cmake-gui 或 ccmake 中显示,方便用户理解。

缓存变量还有一个坑:如果你用 set() 定义了一个同名的普通变量,它会覆盖缓存变量。但缓存变量本身并没有被修改,只是被「遮住」了。这一点我踩过坑,当时死活想不通为什么改了缓存值,程序还是用旧值……

避坑指南: 我曾经在项目里用 set(FOO "bar") 定义了一个普通变量,结果发现 FOO 在 CMakeCache.txt 里也有定义。后来排查了半天,才发现普通变量优先于缓存变量。如果你想让缓存变量生效,要么删掉普通变量的定义,要么用 set(FOO "new_value" CACHE STRING "" FORCE) 强制更新缓存。

5.3 环境变量

环境变量就是操作系统层面的变量,比如 PATHHOMECMAKE_PREFIX_PATH 这些。在 CMake 里,你可以通过 $ENV{变量名} 来读取,用 set(ENV{变量名} 值) 来设置。

message("当前 PATH: $ENV{PATH}")
set(ENV{MY_ENV_VAR} "test")
message("自定义环境变量: $ENV{MY_ENV_VAR}")

不过要注意,CMake 里设置的环境变量只对当前 CMake 进程及其子进程有效,不会影响你系统的环境变量。所以别指望用它来永久修改 PATH

我个人很少在 CMake 里写环境变量,除非是读取系统预设的值。比如检测编译器路径、查找第三方库时,环境变量就很有用。

5.4 变量的作用域

变量的作用域,说白了就是「这个变量在哪些地方能访问到」。CMake 的作用域规则其实很简单:

  • 函数内定义的普通变量:只在函数内可见,函数外看不到。
  • 函数外定义的普通变量:在当前 CMakeLists.txt 及其子目录(通过 add_subdirectory() 引入的)中可见。
  • 缓存变量:全局可见,所有地方都能访问。
  • 环境变量:全局可见,但只对当前进程有效。

举个例子:

set(GLOBAL_VAR "我是全局的")

function(my_func)
  set(LOCAL_VAR "我是局部的")
  message("函数内: ${GLOBAL_VAR}")  # 可以访问
  message("函数内: ${LOCAL_VAR}")   # 可以访问
endfunction()

my_func()
message("函数外: ${GLOBAL_VAR}")  # 可以访问
message("函数外: ${LOCAL_VAR}")   # 访问不到!

你想想看,这个设计其实很合理。函数内部的变量不应该污染外部,外部变量也不应该被函数随意修改。但如果你确实想在函数里修改外部变量,可以用 set(... PARENT_SCOPE)

function(my_func)
  set(GLOBAL_VAR "被修改了" PARENT_SCOPE)
endfunction()

my_func()
message("${GLOBAL_VAR}")  # 输出:被修改了

5.5 set() 和 unset() 命令

set() 我们已经用了很多次了,它的完整语法是这样的:

set(<variable> <value> [CACHE <type> <docstring> [FORCE]])

如果不加 CACHE,就是定义普通变量。加了 CACHE,就是定义缓存变量。加上 FORCE,可以强制覆盖已有的缓存变量。

unset() 就是删除变量,语法类似:

unset(<variable> [CACHE])

如果不加 CACHE,就删除普通变量。如果加了 CACHE,就删除缓存变量。注意,删除缓存变量后,下次 cmake 它会重新出现吗? 不会,除非你又在某个地方用 set() 重新定义了它。

重点总结:
  • set() 定义变量,unset() 删除变量。
  • 普通变量是临时的,缓存变量是持久的。
  • 作用域:函数内局部,函数外全局,缓存变量全局。
  • 环境变量用 $ENV{} 读取,set(ENV{}) 设置。

5.6 知识体系图

下面这张图帮你把整个变量体系串起来,建议保存下来多看几遍。

CMake 变量体系 普通变量 缓存变量 环境变量 临时存在 不写入 CMakeCache.txt 作用域受限 持久存储 写入 CMakeCache.txt 全局可见 系统级变量 $ENV{} 读取 set(ENV{}) 设置 核心命令 set() — 定义变量(普通/缓存) unset() — 删除变量(普通/缓存) $ENV{} / set(ENV{}) — 环境变量操作

5.7 实战建议

最后,给你几个我实际项目中的建议:

  • 能用普通变量就别用缓存变量。缓存变量会污染 CMakeCache.txt,而且容易造成「改了但没生效」的错觉。
  • 用户可配置的选项用缓存变量。比如 BUILD_SHARED_LIBSCMAKE_INSTALL_PREFIX 这些,用户需要在 cmake-gui 里改。
  • 环境变量只读不写。除非你非常清楚自己在做什么,否则别在 CMake 里改环境变量。
  • 函数内修改变量记得用 PARENT_SCOPE。不然你改了也是白改,外面拿不到。

好了,变量这块就讲到这里。你只要记住「普通临时、缓存持久、环境系统」这十二个字,基本就不会搞混了。下一节我们聊聊列表和字符串操作,那也是 CMake 里非常实用的技能。


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