【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
英文地址,請注明出處:http://en.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…