【CMake】缓存变量

目录

一. 缓存变量

二.创建缓存变量

2.1.使用set()来创建缓存变量

2.2.使用FORCE参数来覆盖缓存变量

2.2.1.示例1——不带force的set是不能覆盖已经存在的缓存变量的

2.2.2.示例2——带force的set才能覆盖已经存在的缓存变量

2.2.3.对比示例

2.3.命令行 -D 创建/覆盖缓存变量

2.3.1.直接使用-D来创建/覆盖缓存变量

2.3.2.使用-d来替换掉CMakeLists.txt里面指定的缓存变量

三.缓存变量的作用域

3.1.示例1——全局可见行和全局唯一性

3.2.示例2——全局可见性

四. 缓存变量与普通变量的交互:优先级规则

4.1.示例1——普通变量的设置会“遮盖”缓存变量

4.2.示例2——普通变量的遮盖效应会传递到子作用域


一. 缓存变量

我们去官网看看是怎么说的:set — CMake 4.1.1 Documentation

翻译下来也大概就是下面这样子。

设置 CMake 缓存条目 (Set Cache Entry)

set(<变量名> <值>... CACHE <类型> <说明文字> [FORCE])

这条命令用于在 CMake 中创建或修改一个缓存变量。您可以把缓存变量想象成项目的配置设置,这些设置会被保存下来(在 CMakeCache.txt 文件中),以便下次运行 CMake 时记住用户的选择。正因为它们旨在让用户自定义,所以默认情况下,如果该缓存条目已经存在,set 命令不会覆盖它。如果您希望强制覆盖现有的值,请使用 FORCE 选项。

参数详解:

  • <类型> (必须指定): 定义了变量的类型,它决定了在 cmake-gui 等图形化工具中如何与用户交互。必须是以下类型之一:

    • BOOL: 布尔值,只能是 ON 或 OFF。例如,用来控制是否编译某个功能模块。在 cmake-gui 中会显示为一个复选框,非常直观。

    • FILEPATH: 指向磁盘上某个文件的路径。例如,指定一个外部工具的路径。在 cmake-gui 中会提供一个文件选择对话框,让用户方便地浏览和选择。

    • PATH: 指向磁盘上某个目录的路径。例如,指定第三方库的安装目录。在 cmake-gui 中同样会提供一个目录选择对话框

    • STRING: 一行普通的文本。在 cmake-gui 中显示为一个文本框。如果您还通过 set_property(CACHE <变量名> PROPERTY STRINGS ...) 设置了可选值列表,它则会变成一个下拉选择框,让用户从预定义的选项中选择。

    • INTERNAL: 也是一行文本,但主要用于 CMake 内部使用。这种类型的变量不会显示在 cmake-gui 中,用户无法看到或修改。它通常用于在多次运行 CMake 时在内部持久化存储一些状态或信息。注意:使用此类型隐含了 FORCE 选项,即会自动覆盖旧值。

  • <说明文字> (必须指定): 这是一段简单的描述文字,用于解释这个缓存变量的作用和目的。当用户在 cmake-gui 中将鼠标悬停在变量上时,这段文字就会显示出来,帮助用户理解该如何配置。请务必写得清晰明了。

  • [FORCE] (可选): 就像上面提到的,加上这个选项会强制用新值覆盖已经存在的缓存条目。如果你确信无论之前用户设置成什么,都需要被当前脚本中的值重置,那就使用它。

重要注意事项 (非常重要!):

  1. 变量覆盖规则 (优先级): CMake 中变量的查找规则是:普通变量会覆盖未使用的缓存变量。这意味着,如果你之前用 set(MY_VAR "value")(没有 CACHE)设置了一个普通变量,那么直接读取 MY_VAR 得到的是普通变量的值,而不是缓存变量的值。要访问缓存变量,需要使用 $CACHE{MY_VAR} 语法(CMake 3.13及以上版本)。这是一个非常常见的困惑点!

  2. 处理命令行创建的变量: 用户可能在运行 CMake 时通过 -D<变量>=<值> 的命令行选项创建了一个缓存变量,但没有指定类型。此时,set(... CACHE ...) 命令会为其补充上类型

  3. 路径转换: 如果一个通过 -D 创建的、类型为 PATH 或 FILEPATH 的缓存变量,其值是相对路径(如 ../mylib),那么当 set 命令为其显式设置类型时,CMake 会自动将这个相对路径转换为基于当前工作目录的绝对路径,从而保证路径的准确性。

二.创建缓存变量

2.1.使用set()来创建缓存变量

📂 目录结构

demo/
└── CMakeLists.txt

🔹 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(CacheVarDemo)# 1. 创建一些缓存变量(会写入 build/CMakeCache.txt)
set(MY_BOOL  ON  CACHE BOOL     "是否启用某个功能模块")
set(MY_PATH  "../mylib" CACHE PATH     "第三方库路径")
set(MY_FILE  "../README.md" CACHE FILEPATH "某个文件路径")
set(MY_STR   "hello" CACHE STRING  "一段字符串")# 2. 显示结果
message("MY_BOOL='${MY_BOOL}'")
message("MY_PATH='${MY_PATH}'")
message("MY_FILE='${MY_FILE}'")
message("MY_STR='${MY_STR}'")

其实我们可以一键运行下面这个命令来进行搭建这个目录结构

mkdir -p demo && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheVarDemo)# 1. 创建一些缓存变量(会写入 build/CMakeCache.txt)
set(MY_BOOL  ON  CACHE BOOL     "是否启用某个功能模块")
set(MY_PATH  "../mylib" CACHE PATH     "第三方库路径")
set(MY_FILE  "../README.md" CACHE FILEPATH "某个文件路径")
set(MY_STR   "hello" CACHE STRING  "一段字符串")# 2. 显示结果
message("MY_BOOL='${MY_BOOL}'")
message("MY_PATH='${MY_PATH}'")
message("MY_FILE='${MY_FILE}'")
message("MY_STR='${MY_STR}'")
EOF

demo 目录下执行:

rm -rf build && mkdir build && cd build && cmake ..

大家仔细看看

几乎所有的缓存变量都会被记录到 CMakeCache.txt 文件中。 这个文件本质上就是一个持久化的键值对存储,CMake 的主要目的就是通过这个文件来记住用户的配置选择,从而实现“一次配置,多次使用”,避免每次运行时都重新询问所有配置。

我们现在就去查看CMakeCache.txt中的内容

嗯?怎么这么多别的变量啊?

CMakeCache.txt 文件不仅仅存储了通过 set(... CACHE ...) 或 -D 选项显式设置的变量,它更是一个庞大的数据库,存储了CMake在配置和生成过程中产生的、为了确保下次运行一致所必需的大量内部缓存变量。

那么我们怎么查询我们设置的缓存变量呢?其实我们可以借助grep

grep MY_ CMakeCache.txt

意思是:

  • grep:Linux/Unix 下的文本搜索工具。

  • MY_:要搜索的关键字(这里是匹配所有以 MY_ 开头的变量名)。

  • CMakeCache.txt:要搜索的文件,就是 CMake 生成的缓存文件。

怎么样?还是很容易理解的吧。

2.2.使用FORCE参数来覆盖缓存变量

我们来好好了解一下

set(<variable> <value> CACHE <type> <docstring> [FORCE]) 命令的执行遵循一套严格的规则:

场景一:缓存变量不存在(初次运行)

  • 触发条件:当你第一次在一个空的构建目录中运行 CMake 时,CMakeCache.txt 文件不存在或其中没有名为 <variable> 的条目。

  • CMake 的决策逻辑

    1. 检查:在缓存中查找 <variable>,结果是没找到。

    2. 行动:“用户显然还没有机会设置这个变量。我将把我(开发者)提供的 <value> 作为初始值,并将其持久化到缓存中。”

  • 最终结果:缓存变量 被创建,其值被设置为命令中提供的 <value>。这个值会被写入 CMakeCache.txt 文件,成为一个正式的、可供用户修改的配置选项。

  • 比喻:你提供了一份合同的初稿,因为还没有正式版本,所以初稿直接被采纳为正式合同。

场景二:缓存变量已存在(后续运行)

  • 触发条件:在后续的配置运行中,CMakeCache.txt 文件已经存在,并且包含了名为 <variable> 的条目。它的值可能是:

    • 之前设置的默认值。

    • 用户通过 cmake-gui / ccmake 修改后的值。

    • 用户通过命令行 -D<variable>=<new_value> 设置的值。

  • CMake 的决策逻辑(无 FORCE 关键字)

    1. 检查:在缓存中查找 <variable>,结果“命中”!其当前值为 [cached_value]

    2. 决策:“这个变量已经存在了。这意味着用户可能已经看到了它,并且有机会做出自己的选择。我的职责是提供一个默认值,而不是一个强制值。 既然用户没有表达修改的意图(这次运行没有用新的 -D 重新指定),那么我应该继续信任并保留缓存中现有的值。”

    3. 行动忽略本次 set 命令中提供的 <value>

  • 最终结果:缓存变量的值 保持不变。这次 set 命令实际上成了一个“空操作”。

  • 比喻:用户已经在你给的合同初稿上做了修改并签了字。你不会拿一份新的空白的初稿覆盖掉已经签署的合同。你尊重用户的最终决定。

场景三:强制覆盖(使用 FORCE 关键字)

  • 触发条件:在命令中使用了 FORCE 选项:set(... CACHE ... FORCE)

  • CMake 的决策逻辑

    1. 检查:在缓存中查找 <variable>,结果“命中”!其当前值为 [old_value]

    2. 决策:“虽然这个变量已经存在,但命令中包含了 FORCE 关键字!这说明开发者明确意图要覆盖当前的值,无论用户之前设置过什么。”

    3. 行动:使用本次 set 命令中提供的 <value> 覆盖缓存中现有的值。

  • 最终结果:缓存变量的值 被强制更新 为命令中提供的新 <value>

  • 比喻:这是一个“单方面修订条款”的行为。无论合同之前是什么状态,现在都用这份新的版本强制替换它。

2.2.1.示例1——不带force的set是不能覆盖已经存在的缓存变量的

话不多说,我们先看例子


📂 目录结构

demo/
└── CMakeLists.txt

🔹 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(CacheNoForceDemo)# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")

我们可以通过下面这一条连着的bash语句来搭建这个目录结构和文件

mkdir -p demo && \
cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheNoForceDemo)# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")
EOF

接下来我们就来构建这个项目

mkdir build && cd build && cmake ..

我们发现覆盖前后都是一样的。也就是说,我们的覆盖是失败的。

2.2.2.示例2——带force的set才能覆盖已经存在的缓存变量

好的 👍,我给你一个「对照实验」:同一个项目里,先演示 不带 FORCE 时无法覆盖,再演示 加 FORCE 成功覆盖


📂 目录结构

demo/
└── CMakeLists.txt

🔹 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(CacheForceCompareDemo)# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")# 第三次尝试覆盖(加 FORCE)
set(MY_VAR "third" CACHE STRING "测试变量" FORCE)
message("第三次使用 FORCE 覆盖后 MY_VAR='${MY_VAR}'")

我们可以通过下面这个命令来一键搭建出这个目录结构和文件

mkdir -p demo && \
cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheForceCompareDemo)# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")# 第三次尝试覆盖(加 FORCE)
set(MY_VAR "third" CACHE STRING "测试变量" FORCE)
message("第三次使用 FORCE 覆盖后 MY_VAR='${MY_VAR}'")
EOF

接下来我们就来构建我们的项目

mkdir build && cd build && cmake ..

我们发现,每一次打印的结果都是不一样的,这就更加说明了我们的猜想


  • 不带 FORCE → 已存在的缓存值不会被覆盖。
  • 带 FORCE → 立刻覆盖缓存里的旧值。

2.2.3.对比示例

我们可以写一个例子,用 两个缓存变量,分别演示 不使用 FORCE使用 FORCE 的效果。


📂 目录结构

demo/
└── CMakeLists.txt

🔹 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(CacheTwoVarsDemo)# 第一次创建两个缓存变量
set(VAR1 "first1" CACHE STRING "第一个缓存变量")
set(VAR2 "first2" CACHE STRING "第二个缓存变量")# 尝试修改,不加 FORCE
set(VAR1 "second1" CACHE STRING "第一个缓存变量")
set(VAR2 "second2" CACHE STRING "第二个缓存变量" FORCE)# 打印结果
message("VAR1='${VAR1}' (不使用 FORCE)")
message("VAR2='${VAR2}' (使用 FORCE)")


其实我们可以通过下面这个目录来一键构建出这个目录结构和文件

mkdir -p demo && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheTwoVarsDemo)# 第一次创建两个缓存变量
set(VAR1 "first1" CACHE STRING "第一个缓存变量")
set(VAR2 "first2" CACHE STRING "第二个缓存变量")# 尝试修改,不加 FORCE
set(VAR1 "second1" CACHE STRING "第一个缓存变量")
set(VAR2 "second2" CACHE STRING "第二个缓存变量" FORCE)# 打印结果
message("VAR1='${VAR1}' (不使用 FORCE)")
message("VAR2='${VAR2}' (使用 FORCE)")
EOF

接下来我们就来构建项目

rm -rf build && mkdir build && cd build && cmake ..

 🔹 解释

  • VAR1第一次创建是 "first1",之后修改 "second1" 没有 FORCE,所以缓存保持 "first1"
  • VAR2第一次创建是 "first2",修改 "second2" 使用 FORCE,缓存被覆盖成 "second2"。  

我们可以去CMakeChace.txt里面看看

  

和我们的运行结果可是一模一样的。

2.3.命令行 -D 创建/覆盖缓存变量

2.3.1.直接使用-D来创建/覆盖缓存变量

📂 目录结构

demo/
└── CMakeLists.txt

这里的 CMakeLists.txt 可以几乎空,但保留一个最小声明:

cmake_minimum_required(VERSION 3.15)
project(DCacheDemo)

接下来我们就来看看

rm -rf build && mkdir build && cd build

  

接下来我们就使用 -D 创建新缓存变量

cmake .. -DMY_NEW_VAR="hello"

  

我们现在就可以去CMakeCache.txt里面查看这个缓存变量

grep MY_NEW_VAR CMakeCache.txt

  

跟我们设置的一模一样的。


接下来我们将使用 -D 覆盖已有缓存变量

然后运行:

cmake .. -DMY_NEW_VAR="hello world"

  

我们现在就可以去CMakeCache.txt里面查看这个缓存变量

grep MY_NEW_VAR CMakeCache.txt

  

怎么样?

2.3.2.使用-d来替换掉CMakeLists.txt里面指定的缓存变量

📂 目录结构

demo/
└── CMakeLists.txt

🔹 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(DOverrideDemo)# 定义一个缓存变量
set(MY_OPTION "from_cmakelists" CACHE STRING "演示缓存变量")# 打印最终结果
message("MY_OPTION='${MY_OPTION}'")

我们可以一键搭建这个项目的目录结构和文件

mkdir -p demo && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(DOverrideDemo)# 定义一个缓存变量
set(MY_OPTION "from_cmakelists" CACHE STRING "演示缓存变量")# 打印结果
message("MY_OPTION='${MY_OPTION}'")
EOF

接下来我们来构建一下这个项目

rm -rf build && mkdir build && cd build && cmake ..

现在我们可以去CMakeCache.txt 里看看对应条目:

接下来我们使用 -D 覆盖 CMakeLists.txt 中的缓存变量

cmake .. -DMY_OPTION="from_commandline"

这个时候我们回去那个CMakeCache.txt 看看这个条目被更新为:

三.缓存变量的作用域

在 CMake 的变量体系中,缓存变量是一个特殊的存在,它完全超越了普通变量所遵循的“目录作用域”规则。

您可以将其理解为项目配置中的全局变量持久化设置

它的核心特征在于其全局可见性持久化存储,这与普通变量的局部性和临时性形成了鲜明对比。

核心特性:全局唯一与持久化

  1. 全局唯一性(单一事实来源)
    整个 CMake 项目中,任何一个特定的缓存变量有且只有一个。它被存储在一个独立的、全局的存储区中,通常被视为所有目录作用域之上的一个共享层。无论您在当前目录、子目录,还是父目录中读取一个名为 MY_CACHE_VAR 的缓存变量,您访问的都是同一个全局实体。它的值在任何地方、任何时候(在一次配置过程中)都是一致的。

  2. 无视目录作用域隔离
    这是缓存变量与普通变量最根本的区别。普通变量严格遵守目录作用域的“向下继承,向上隔离”规则。而缓存变量则完全无视这堵“墙”。

    • 读操作:在任何目录作用域中读取缓存变量,得到的都是其全局唯一的值。

    • 写操作:在任何目录作用域中修改缓存变量的值,都会立即更新这个全局唯一的值,并且这个更改立刻对所有其他目录作用域可见。在一个子目录中修改了缓存变量,父目录或其他兄弟目录在随后读取它时,会立刻得到这个新值。这彻底打破了普通变量那种“修改互不影响”的隔离性。

  3. 持久化存储(跨运行存在)
    缓存变量的值不会被保存在 CMakeLists.txt 文件里,而是会被写入到 CMake 构建目录下的 CMakeCache.txt 文件中。这个文件是 CMake 的“记忆中心”。这意味着:

    • 一旦一个缓存变量被设置,它的值会在您多次运行 cmake 配置命令的过程中持续存在。

    • 这正是图形化配置工具(如 cmake-gui 或 ccmake)能够展示并允许用户修改的变量列表。

    • 要清除一个缓存变量,必须手动删除构建目录、在 GUI 中操作,或使用 unset(... CACHE) 命令。

3.1.示例1——全局可见行和全局唯一性

📂 目录结构

demo/
├── CMakeLists.txt
└── sub/├── CMakeLists.txt└── subsub/└── CMakeLists.txt

🔹 顶层 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(GlobalCacheVarDemo)set(MY_CACHE_VAR "from_top" CACHE STRING "演示全局缓存变量" FORCE)
message("顶层看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")add_subdirectory(sub)message("顶层再次看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")

🔹 子目录 demo/sub/CMakeLists.txt

message("子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")set(MY_CACHE_VAR "from_sub" CACHE STRING "演示全局缓存变量" FORCE)
message("子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")add_subdirectory(subsub)# 在孙子目录修改完之后,再次查看
message("子目录返回后 MY_CACHE_VAR='${MY_CACHE_VAR}'")

🔹 孙子目录 demo/sub/subsub/CMakeLists.txt

message("孙子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")set(MY_CACHE_VAR "from_subsub" CACHE STRING "演示全局缓存变量" FORCE)
message("孙子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")

其实我们可以通过一行 bash(一次性创建文件并运行)来快速搭建这个目录结构和文件。

mkdir -p demo/sub/subsub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(GlobalCacheVarDemo)set(MY_CACHE_VAR "from_top" CACHE STRING "演示全局缓存变量" FORCE)
message("顶层看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")add_subdirectory(sub)message("顶层再次看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")
EOFcat > demo/sub/CMakeLists.txt <<'EOF'
message("子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")set(MY_CACHE_VAR "from_sub" CACHE STRING "演示全局缓存变量" FORCE)
message("子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")add_subdirectory(subsub)message("子目录返回后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
EOFcat > demo/sub/subsub/CMakeLists.txt <<'EOF'
message("孙子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")set(MY_CACHE_VAR "from_subsub" CACHE STRING "演示全局缓存变量" FORCE)
message("孙子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
EOF

接下来我们就来构建我们的项目

mkdir build && cd build && cmake ..

大家仔细观察,就会发现这3个CMakeLists.txt里面操作的都是同一个缓存变量!!!!这就验证了缓存变量的全局可见性和全局唯一性。

注意:如果我们不在set里面加force,运行结果就会是下面这样子。

3.2.示例2——全局可见性

接下来我们将

  • 每一层目录都各自 set 一个不同名字的缓存变量(例如 TOP_CACHE_VARSUB_CACHE_VARSUBSUB_CACHE_VAR)。

  • 并且在 每一层打印出 全部 3 个变量,直观演示「缓存变量是全局唯一的」特性。


📂 目录结构

demo/
├── CMakeLists.txt
└── sub/├── CMakeLists.txt└── subsub/└── CMakeLists.txt

🔹 demo/CMakeLists.txt (顶层)

cmake_minimum_required(VERSION 3.15)
project(CacheThreeVarsDemo)# 顶层设置一个缓存变量
set(TOP_CACHE_VAR "set_in_top" CACHE STRING "顶层缓存变量")message("[顶层] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")# 进入子目录
add_subdirectory(sub)message("[顶层] 返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")

🔹 demo/sub/CMakeLists.txt (子目录)

message("[子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")# 设置子目录自己的缓存变量
set(SUB_CACHE_VAR "set_in_sub" CACHE STRING "子目录缓存变量")message("[子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")# 进入孙子目录
add_subdirectory(subsub)message("[子目录] 从孙子返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")

🔹 demo/sub/subsub/CMakeLists.txt (孙子目录)

message("[孙子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")# 设置孙子目录自己的缓存变量
set(SUBSUB_CACHE_VAR "set_in_subsub" CACHE STRING "孙子目录缓存变量")message("[孙子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")

我们可以一键复制下面的 Bash 语句来创建我们的目录结构和文件

mkdir -p demo/sub/subsub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheThreeVarsDemo)set(TOP_CACHE_VAR "set_in_top" CACHE STRING "顶层缓存变量")
message("[顶层] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
add_subdirectory(sub)
message("[顶层] 返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
EOFcat > demo/sub/CMakeLists.txt <<'EOF'
message("[子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
set(SUB_CACHE_VAR "set_in_sub" CACHE STRING "子目录缓存变量")
message("[子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
add_subdirectory(subsub)
message("[子目录] 从孙子返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
EOFcat > demo/sub/subsub/CMakeLists.txt <<'EOF'
message("[孙子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
set(SUBSUB_CACHE_VAR "set_in_subsub" CACHE STRING "孙子目录缓存变量")
message("[孙子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
EOF

接下来我们就来构建我们的项目

mkdir build && cd build && cmake ..


这样你就能看到:

  1. 每层定义的缓存变量 对全局都可见

  2. 即使是后设置的变量(SUB_CACHE_VARSUBSUB_CACHE_VAR),顶层最后也能读到它们的值。

要不要我再帮你加一个 普通变量版本(不带 CACHE),并打印对比差异?

四. 缓存变量与普通变量的交互:优先级规则

当一个变量名既作为普通变量存在,又作为缓存变量存在时,CMake 遵循一条明确的优先级规则

  • 普通变量的设置会“遮盖”缓存变量

  •  一旦通过`set(MY_VAR "value")`这样的语句在当前作用域内定义了一个普通变量该作用域及其所有由此向下延伸的子作用域(通过`add_subdirectory()`或`function()`调用进入的新作用域)中,任何对`${MY_VAR}`的求值操作都会直接返回这个新设置的普通变量的值。缓存中存储的值依然完好无损地存在于全局缓存中,只是在当前的变量解析路径上被暂时地“绕过”了。

  • 但是,这个“遮盖”效应是局部的,仅限于当前目录作用域其子作用域。一旦跳出这个范围,仍然可以访问到底层缓存变量的值。

  • 为了提供一种显式且可靠的访问方式,不受当前作用域内普通变量的干扰,CMake引入了`$CACHE{MY_VAR}`语法。这是一种强制的、指向性的访问。如果您使用 $CACHE{MY_VAR} 语法(CMake 3.13+),无论你在哪个作用域,仍然可以访问到底层缓存变量的值。它明确指示CMake解释器绕过所有当前作用域内的普通变量查找,直接访问全局缓存命名空间并获取其中存储的值

4.1.示例1——普通变量的设置会“遮盖”缓存变量

好 👌 我给你写一个最简单的例子,演示 同名普通变量和缓存变量的优先级关系


📂 目录结构

demo/
├── CMakeLists.txt
└── sub/└── CMakeLists.txt

🔹 顶层 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)# 定义一个缓存变量
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")# 进入子目录
add_subdirectory(sub)# 回到顶层后,再次读取
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")

🔹 子目录 demo/sub/CMakeLists.txt

# 子目录开始
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")# 定义同名的普通变量(遮盖缓存变量)
set(MY_VAR "normal_value")message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")# 如果想显式访问缓存值(CMake 3.13+ 支持)
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")

🔹 一行 Bash 运行

mkdir -p demo/sub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")
message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")
add_subdirectory(sub)
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")
EOFcat > demo/sub/CMakeLists.txt <<'EOF'
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")
set(MY_VAR "normal_value")
message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")
EOF

接下来我们就来构建我们的项目

mkdir build && cd build && cmake ..

输出

这也就说明了

当一个普通变量被设置成一个与缓存变量同名的名字时,在其作用域内普通变量的值会“遮盖”(Shadow)掉缓存变量的值。这意味着,直接使用 ${MY_VAR} 将会访问到普通变量的值。

但是,缓存变量本身的值并没有被改变,它依然安全地存储在 CMakeCache.txt 中。一旦离开了那个普通变量的作用域(例如,从子目录返回到父目录),${MY_VAR} 又会重新指向那个未被改变的缓存变量的值。

我们来仔细讲解一下

1.顶层目录,初始阶段

  • set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")

    • 这行代码定义了一个名为 MY_VAR 的缓存变量,其值为 "cache_value"

  • message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")

    • 此时,${MY_VAR} 读取到的就是这个缓存变量的值。

    • 输出[顶层] 初始: MY_VAR='cache_value' (缓存)

2.进入子目录 sub/

  • add_subdirectory(sub) 命令执行,CMake 开始处理 sub/CMakeLists.txt

  • message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")

    • 子目录继承了父目录的作用域。此时还没有同名的普通变量,所以 ${MY_VAR} 仍然解析为缓存变量的值。

    • 输出[子目录] 进入时: MY_VAR='cache_value' (继承自缓存)

3.在子目录中设置普通变量

  • set(MY_VAR "normal_value") (注意:没有 CACHE 关键字)

    • 这行代码定义了一个同名的普通变量。根据“遮盖”规则,从现在开始,在当前目录(子目录)的作用域内${MY_VAR} 将指向这个新的普通变量。

  • message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")

    • 正如规则所述,它现在读取到的是普通变量的值 "normal_value"

    • 输出[子目录] 设置普通变量后: MY_VAR='normal_value' (普通变量覆盖缓存)

4.显式访问缓存变量

  • message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")

    • CMake 3.13 引入了 $CACHE{VARNAME} 语法,允许你显式地、直接地访问缓存变量的值,绕过任何可能存在的同名普通变量。

    • 所以,这里它成功地读取到了被“遮盖”的缓存变量的原始值 "cache_value"

    • 输出[子目录] 显式访问缓存: cache_value

    • 这个操作非常重要,它证明了缓存变量 MY_VAR 的值自始至终都没有被改变过。

5.返回顶层目录

  • 子目录的 CMakeLists.txt 处理完毕,CMake 返回到顶层目录继续执行。

  • 当离开子目录的作用域时,在那个作用域内定义的普通变量 MY_VAR(值为 "normal_value")就被销毁了

  • message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")

    • 现在,${MY_VAR} 前面已经没有同名的普通变量来遮盖它了,所以它再次清晰地指向了那个全局的、一直未变的缓存变量

    • 输出[顶层] 返回时: MY_VAR='cache_value' (缓存)

4.2.示例2——普通变量的遮盖效应会传递到子作用域

那我在上一个例子的基础上再加一个 孙子目录,让你清楚看到:

  • 普通变量的遮盖效应会传递到子作用域(子目录、孙子目录),

  • 但是一旦跳出当前作用域,就恢复为缓存变量。


📂 目录结构

demo/
├── CMakeLists.txt
└── sub/├── CMakeLists.txt└── subsub/└── CMakeLists.txt

🔹 顶层 demo/CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)# 定义一个缓存变量
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")# 进入子目录
add_subdirectory(sub)# 回到顶层后,再次读取
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")

🔹 子目录 demo/sub/CMakeLists.txt

# 子目录开始
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")# 定义同名普通变量 → 遮盖缓存变量
set(MY_VAR "normal_value")message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")# 进入孙子目录
add_subdirectory(subsub)# 回到子目录后
message("[子目录] 返回时: MY_VAR='${MY_VAR}' (普通变量还在遮盖缓存)")

🔹 孙子目录 demo/sub/subsub/CMakeLists.txt

# 孙子目录开始
message("[孙子目录] 进入时: MY_VAR='${MY_VAR}' (继承了子目录的普通变量覆盖)")
message("[孙子目录] 显式访问缓存: $CACHE{MY_VAR}")

我们可以直接复制下面这个代码去一键构建出目录结构和文件

mkdir -p demo/sub/subsub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")
message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")
add_subdirectory(sub)
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")
EOFcat > demo/sub/CMakeLists.txt <<'EOF'
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")
set(MY_VAR "normal_value")
message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")
add_subdirectory(subsub)
message("[子目录] 返回时: MY_VAR='${MY_VAR}' (普通变量还在遮盖缓存)")
EOFcat > demo/sub/subsub/CMakeLists.txt <<'EOF'
message("[孙子目录] 进入时: MY_VAR='${MY_VAR}' (继承了子目录的普通变量覆盖)")
message("[孙子目录] 显式访问缓存: $CACHE{MY_VAR}")
EOF

接下来我们就来构建这个项目

mkdir build && cd build && cmake ..


✅ 这样你就能清楚看到:

  • 子目录定义的普通变量会传递到孙子目录,继续遮盖缓存变量。

  • 但是回到顶层时,普通变量作用域消失,重新读取的是缓存值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/96344.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96344.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue2使用若依框架动态新增tab页并存储之前的tab页的操作

1. 应用场景&#xff1a;点击历史记录&#xff0c;要比较两个tab页的内容时&#xff0c;需要做到切换tab页来回看左右对数据对比。2.开发难点若依项目正常是把路由配置到菜单管理里&#xff0c;都是设定好的。不过它也给我们写好了动态新增tab页的方&#xff0c;需要我们自己来…

论文阅读-SelectiveStereo

文章目录1 概述2 模块2.1 SelectiveIGEV和IGEV的差异2.2 上下文空间注意力2.2.1 通道注意力2.2.2 空间注意力2.3 SRU3 效果参考资料1 概述 本文主要结合代码对Selective的创新点进行针对性讲解&#xff0c;相关的背景知识可以参考我写的另两篇文章论文阅读-RaftStereo和论文阅…

深入分析神马 M56S+ 202T 矿机参数与性能特点

引言在比特币&#xff08;BTC&#xff09;和比特币现金&#xff08;BCH&#xff09;等主流加密货币的挖掘过程中&#xff0c;矿机的选择直接关系到挖矿的效率与收益。神马 M56S 202T矿机是SHA-256算法的矿机&#xff0c;凭借其强大的算力和高效的能效比&#xff0c;成为了矿工们…

36.2Linux单总线驱动DS18B20实验(详细讲解代码)_csdn

想必看过我很多次博客的同学&#xff0c;都知道了编写驱动的流程&#xff01; 这里我们还是按照以前的习惯来一步一步讲解&#xff01; 单总线驱动&#xff0c;在F103和51单片机的裸机开发中是经常见的。 linux驱动代码编写实际上就是&#xff0c;端对端的编程&#xff01; 就是…

【杂类】应对 MySQL 处理短时间高并发的请求:缓存预热

一、什么是缓存预热&#xff1f;1. 核心概念​​缓存预热&#xff08;Cache Warm-up&#xff09;​​ 是指在系统​​正式对外提供服务之前​​&#xff0c;或​​某个高并发场景来临之前​​&#xff0c;​​主动​​将后续极有可能被访问的热点数据从数据库&#xff08;MySQL…

点评项目(Redis中间件)第三部分短信登录,查询缓存

可以直接看后面Redis实现功能的部分基于session实现短信登录发送短信验证码前端请求样式业务层代码Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {Overridepublic Result sendCode(String phone, HttpSession se…

线性方程求解器的矩阵分裂

大概思路是对的&#xff0c;但是查老师可能会出现幻觉&#xff0c;小心食用 &#x1f603;这段代码是在初始化迭代法求解器&#xff0c;构建迭代矩阵和分裂矩阵。以下是详细解释&#xff1a; if init_from_func or init_from_input:# 1. 存储刚度矩阵self.stiff_p stiff_p# 2.…

【Beetle RP2350】雷达模块 CEM5861G-M11 开发使用指南

一、硬件介绍 1、产品特点 Beetle RP2350【RP2350A_QFN60】是一款基于RP2350微控制器的高性能迷你开发板&#xff0c;双核双架构设计&#xff08;支持 Arm Cortex-M33或Hazard3 RISC-V内核&#xff09;为开发者提供灵活的性能配置。 双核双架构&#xff0c;性能自由切换 采…

高通Android 13 开机黑屏问题深度剖析与解决方案

1. 问题概述 在 Android 13 系统定制化开发过程中&#xff0c;开机流程的调试与优化颇具挑战性。一个典型问题是&#xff1a;当开机动画播放完毕后&#xff0c;设备会先出现数秒黑屏&#xff0c;然后才进入锁屏界面。本文基于开机日志分析&#xff0c;结合实际项目经验&#x…

腾讯推出AI CLI工具CodeBuddy,国内首家同时支持插件、IDE和CLI三种形态的AI编程工具厂商

2025年9月9日&#xff0c;腾讯正式推出自研AI CLI工具CodeBuddy code&#xff0c;成为国内首家同时支持插件、IDE和CLI三种形态的AI编程工具厂商。这一创新不仅填补了国内市场在全形态AI编程工具领域的空白&#xff0c;更以编码时间缩短40%、AI生成代码占比超50%的硬核数据&…

零基础学习QT的第二天-组件基础知识

组件声明以及设置属性 所有的组件的基类为&#xff1a;QtObject&#xff0c;在c中名称为&#xff1a;QObject。 在qml和c名称有所区别&#xff0c;例如在Qml中QtObject&#xff0c;在C会省略一个t(QObject) 声明组件的方式&#xff1a; 组件名 {属性名:值}在实际应用中&#xf…

像素图生成小程序开发全解析:从图片上传到Excel图纸

像素图生成小程序开发全解析&#xff1a;从图片上传到Excel图纸 前言 在数字化创作和工艺设计领域&#xff0c;像素图生成工具具有广泛的应用价值&#xff0c;无论是十字绣设计、LED灯阵布置还是复古游戏美术创作。本文将详细解析一个功能完整的像素图生成小程序的开发过程&…

mac-intel操作系统go-stock项目(股票分析工具)安装与配置指南

1. 项目基础介绍 go-stock 是一个基于Wails和NaiveUI开发的AI赋能股票分析工具。旨在为用户提供自选股行情获取、成本盈亏展示、涨跌报警推送等功能。它支持A股、港股、美股等市场&#xff0c;能够进行市场整体或个股的情绪分析、K线技术指标分析等功能。所有数据均保存在本地…

spring-单例bean是线程安全的吗

其中可修改的成员变量有线程不安全问题&#xff0c;不可修改的无状态的 userService是没有线程安全问题的 spring框架中有一个 Scope注解&#xff0c;默认的值就是singleton&#xff0c;单例的。 不是线程安全的&#xff0c;一般来说&#xff0c;我们在bean中注入的对象都是无状…

CM1033系列 3串锂电池保护IC - 高精度±25mV 内置延时 多型号可选(含铁锂)

1. 核心亮点 高精度多重保护&#xff1a;专为3串电池组设计&#xff0c;提供过充、过放、三级过流&#xff08;含短路&#xff09;、充电过流及断线检测等全方位保护&#xff0c;电压检测精度高达25mV。超低功耗&#xff1a;工作电流典型值仅7μA&#xff0c;休眠电流低至4μA&…

【第23话:定位建图】SLAM后端优化方法详解

SLAM 后端优化方法详解 SLAM&#xff08;Simultaneous Localization and Mapping&#xff09;后端优化是SLAM系统中的关键环节&#xff0c;负责对前端输出的传感器数据进行全局一致性优化&#xff0c;消除累积误差。后端通常基于图优化框架&#xff08;如g2o、GTSAM&#xff09…

MongoDB 备份与恢复终极指南:mongodump 和 mongorestore 深度实战

MongoDB 备份与恢复终极指南&#xff1a;mongodump 和 mongorestore 深度实战引言&#xff1a;数据守护者的使命第一部分&#xff1a;基础概念与核心原理1.1 逻辑备份 vs. 物理备份&#xff1a;根本性的区别1.2 核心工具介绍第二部分&#xff1a;mongodump 备份实战详解2.1 基础…

鸿蒙的“分布式架构”理念:未来操作系统的关键突破

一、引言&#xff1a;为什么需要分布式架构&#xff1f; 随着移动互联网的发展&#xff0c;智能设备不断普及。用户身边可能同时拥有 手机、平板、PC、电视、手表、耳机、智能音箱、车机 等多种终端设备。 但现实中&#xff0c;我们常遇到以下问题&#xff1a; 不同设备系统割…

MySQL 事务管理与锁机制:解决并发场景下的数据一致性问题

前言在电商下单、金融转账、库存扣减等并发业务场景中&#xff0c;若不控制数据操作的原子性与隔离性&#xff0c;极易出现 “超卖”“重复扣款”“脏读数据” 等问题。MySQL 的事务管理与锁机制是解决这些问题的核心技术&#xff0c;也是后端开发者必须掌握的生产环境能力。本…

MySQL集群高可用架构

一、MySQL高可用之组复制&#xff08;MGR&#xff09;1.1 组复制核心特性与优势MySQL Group Replication&#xff08;MGR&#xff09;是基于分布式一致性协议&#xff08;Paxos&#xff09;实现的高可用集群方案&#xff0c;核心特性包括&#xff1a;自动故障检测与恢复&#x…