目錄
CMake語法—宏和函數(macro vs function)
1 宏macro定義與應用
macro(<name> [<arg1> ...])
<commands>
endmacro()
- macro:宏關鍵字
- name:宏名稱
- arg1:宏參數
宏的定義與使用方式與函數相同,可參考隨筆進行簡單理解。本文側重對比宏與函數區別。
2 宏與函數區別
2.1 示例代碼結構

-
learn_cmake:為根目錄
-
build:為CMake配置輸出目錄(在此例中即生成sln解決方案的地方)
-
CMakeLists.txt:CMake腳本
-
cmake_config.bat:執行CMake配置過程的腳本(雙擊直接運行),公用代碼如下:
@echo off set currentDir=%~dp0 set buildDir=%currentDir% set cmakeOutputDir=%currentDir%\build cmake -S %buildDir% -B %cmakeOutputDir% -G"Visual Studio 16 2019" -T v140 -A x64 pause
2.2 區別1:函數會產生新作用域;宏是把執行代碼替換到調用位置
2.2.1 示例代碼(CMakeLists.txt)
cmake_minimum_required(VERSION 3.18)
# 設置工程名稱
set(PROJECT_NAME KAIZEN)
# 設置工程版本號
set(PROJECT_VERSION "1.0.0.10" CACHE STRING "默認版本號")
# 工程定義
project(${PROJECT_NAME}
LANGUAGES CXX C
VERSION ${PROJECT_VERSION}
)
# 打印開始日志
message(STATUS "\n########## BEGIN_TEST_MACRO_VS_FUNCTION")
# 定義函數
function(test_func_argument age)
# 打印ARGN參數值
message(STATUS "ARGN: ${ARGN}")
# 打印ARGC參數值
message(STATUS "ARGC: ${ARGC}")
# 打印ARGV參數值
message(STATUS "ARGV: ${ARGV}")
# 打印ARGV0參數值
message(STATUS "ARGV0: ${ARGV0}")
# 打印參數個數
list(LENGTH ARGV argv_len)
message(STATUS "length of ARGV: ${argv_len}")
# 遍歷打印各參數值
set(i 0)
while(i LESS ${argv_len})
list(GET ARGV ${i} argv_value)
message(STATUS "argv${i}:${argv_value}")
math(EXPR i "${i} + 1")
endwhile()
if (ARGV1) # ARGV1 is a true variable
message(STATUS "ARGV1: ${ARGV1}")
endif()
if (DEFINED ARGV2) # ARGV2 is a true variable
message(STATUS "ARGV2: ${ARGV2}")
endif()
if (ARGC GREATER 2) # ARGC is a true variable
message(STATUS "ARGC: ${ARGC}")
endif()
foreach (loop_var IN LISTS ARGN) # ARGN is a true variable
message(STATUS "var: ${loop_var}")
endforeach()
endfunction()
# 定義宏
macro(test_macro_argument age)
# 打印ARGN參數值
message(STATUS "ARGN: ${ARGN}")
# 打印ARGC參數值
message(STATUS "ARGC: ${ARGC}")
# 打印ARGV參數值
message(STATUS "ARGV: ${ARGV}")
# 打印ARGV0參數值
message(STATUS "ARGV0: ${ARGV0}")
# 打印參數個數
list(LENGTH ARGV argv_len)
message(STATUS "length of ARGV: ${argv_len}")
# 遍歷打印各參數值
set(i 0)
while(i LESS ${argv_len})
list(GET ARGV ${i} argv_value)
message(STATUS "argv${i}:${argv_value}")
math(EXPR i "${i} + 1")
endwhile()
if (ARGV1) # ARGV1 is not a variable
message(STATUS "ARGV1: ${ARGV1}")
endif()
if (DEFINED ARGV2) # ARGV2 is not a variable
message(STATUS "ARGV2: ${ARGV2}")
else()
message(STATUS "not defined ARGV2")
endif()
if (ARGC GREATER 2) # ARGC is not a variable
message(STATUS "ARGC: ${ARGC}")
endif()
foreach (loop_var IN LISTS ARGN) # ARGN is not a variable
message(STATUS "var: ${loop_var}")
endforeach()
endmacro()
test_func_argument(22 33 44)
message(STATUS "\n")
test_macro_argument(22 33 44)
# 打印結束日志
message(STATUS "########## END_TEST_MACRO_VS_FUNCTION\n")
2.2.2 運行結果
-- Selecting Windows SDK version 10.0.18362.0 to target Windows 10.0.17763.
-- The CXX compiler identification is MSVC 19.0.24245.0
-- The C compiler identification is MSVC 19.0.24245.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
--
########## BEGIN_TEST_MACRO_VS_FUNCTION
-- ARGN: 33;44
-- ARGC: 3
-- ARGV: 22;33;44
-- ARGV0: 22
-- length of ARGV: 3
-- argv0:22
-- argv1:33
-- argv2:44
-- ARGV1: 33
-- ARGV2: 44
-- ARGC: 3
-- var: 33
-- var: 44
--
-- ARGN: 33;44
-- ARGC: 3
-- ARGV: 22;33;44
-- ARGV0: 22
-- length of ARGV: 0
-- not defined ARGV2
-- ########## END_TEST_MACRO_VS_FUNCTION
-- Configuring done
-- Generating done
-- Build files have been written to: F:/learn_cmake/build
請按任意鍵繼續. . .
2.2.3 說明
從示例及運行結果可知:與函數相比,在宏中ARGV、ARGV1、ARGV2、ARGC、ARGN都不是真實的變量。
那么,示例程序中第61至67行,輸出第31至34行怎么來的呢?宏替換的作用。如果不理解,需要回去惡補一下C語言的宏。
2.3 區別2:函數內可以使用return;宏中不建議使用return
2.3.1 示例代碼(CMakeLists.txt)
cmake_minimum_required(VERSION 3.18)
# 設置工程名稱
set(PROJECT_NAME KAIZEN)
# 設置工程版本號
set(PROJECT_VERSION "1.0.0.10" CACHE STRING "默認版本號")
# 工程定義
project(${PROJECT_NAME}
LANGUAGES CXX C
VERSION ${PROJECT_VERSION}
)
# 打印開始日志
message(STATUS "\n########## BEGIN_TEST_MACRO_VS_FUNCTION")
# 定義函數
function(test_func_argument age)
# 打印ARGN參數值
message(STATUS "ARGN: ${ARGN}")
# 打印ARGC參數值
message(STATUS "ARGC: ${ARGC}")
# 打印ARGV參數值
message(STATUS "ARGV: ${ARGV}")
# 打印ARGV0參數值
message(STATUS "ARGV0: ${ARGV0}")
# 打印參數個數
list(LENGTH ARGV argv_len)
message(STATUS "length of ARGV: ${argv_len}")
if (argv_len GREATER 3)
foreach (loop_var IN LISTS ARGV) # ARGV is a true variable
message(STATUS "loop_var: ${loop_var}")
endforeach()
else()
message(STATUS "in func exec return")
return() ## 從此退出
endif()
message(STATUS "after return")
endfunction()
# 定義宏
macro(test_macro_argument age)
# 打印ARGN參數值
message(STATUS "ARGN: ${ARGN}")
# 打印ARGC參數值
message(STATUS "ARGC: ${ARGC}")
# 打印ARGV參數值
message(STATUS "ARGV: ${ARGV}")
# 打印ARGV0參數值
message(STATUS "ARGV0: ${ARGV0}")
# 定義一個變量
set(list_var "${ARGV}")
# 打印參數個數
list(LENGTH list_var argv_len)
message(STATUS "length of ARGV: ${argv_len}")
if (argv_len GREATER 3)
foreach (loop_var IN LISTS list_var) # list_var is a true variable
message(STATUS "loop_var: ${loop_var}")
endforeach()
else()
message(STATUS "in macro exec return")
return() ## 從此退出
endif()
message(STATUS "after return")
endmacro()
test_func_argument(22 44 66 88 100)
message(STATUS "\n")
test_func_argument(10 11 12)
message(STATUS "\n")
test_macro_argument(11 33 55 77 99)
message(STATUS "\n")
message(STATUS "after exec macro with 5 value to continue\n")
test_macro_argument(20 21 22)
message(STATUS "\n")
message(STATUS "after exec macro with 3 value to continue")
# 打印結束日志
message(STATUS "########## END_TEST_MACRO_VS_FUNCTION\n")
2.3.2 運行結果
-- Selecting Windows SDK version 10.0.18362.0 to target Windows 10.0.17763.
-- The CXX compiler identification is MSVC 19.0.24245.0
-- The C compiler identification is MSVC 19.0.24245.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
--
########## BEGIN_TEST_MACRO_VS_FUNCTION
-- ARGN: 44;66;88;100
-- ARGC: 5
-- ARGV: 22;44;66;88;100
-- ARGV0: 22
-- length of ARGV: 5
-- loop_var: 22
-- loop_var: 44
-- loop_var: 66
-- loop_var: 88
-- loop_var: 100
-- after return
--
-- ARGN: 11;12
-- ARGC: 3
-- ARGV: 10;11;12
-- ARGV0: 10
-- length of ARGV: 3
-- in func exec return
--
-- ARGN: 33;55;77;99
-- ARGC: 5
-- ARGV: 11;33;55;77;99
-- ARGV0: 11
-- length of ARGV: 5
-- loop_var: 11
-- loop_var: 33
-- loop_var: 55
-- loop_var: 77
-- loop_var: 99
-- after return
--
-- after exec macro with 5 value to continue
-- ARGN: 21;22
-- ARGC: 3
-- ARGV: 20;21;22
-- ARGV0: 20
-- length of ARGV: 3
-- in macro exec return
-- Configuring done
-- Generating done
-- Build files have been written to: F:/learn_cmake/build
請按任意鍵繼續. . .
2.3.3 小結
從示例及運行結果可知:與函數相比,如果在宏中執行return命令之后,整個CMake進程會退出執行,不再繼續執行其他語句。
因為函數會產生新的作用域,在函數中return只意味着退出函數作用域,父作用域的代碼語句會正常執行。
而宏只是等價替換,當調用宏時,相當於把宏中命令語句替換到相應的位置,所以宏中return,也意味着會退出整個進程。
另外,在此示例程序中第57行,我們定義了一個普通變量list_var,主要作用:為了解決宏中沒有ARGV的”尷尬“,可自行體會這種用法。
2.4 區別3:在函數中調用宏的精妙
2.4.1 示例代碼(CMakeLists.txt)
cmake_minimum_required(VERSION 3.18)
# 設置工程名稱
set(PROJECT_NAME KAIZEN)
# 設置工程版本號
set(PROJECT_VERSION "1.0.0.10" CACHE STRING "默認版本號")
# 工程定義
project(${PROJECT_NAME}
LANGUAGES CXX C
VERSION ${PROJECT_VERSION}
)
# 打印開始日志
message(STATUS "\n########## BEGIN_TEST_MACRO_VS_FUNCTION")
macro(bar)
if (DEFINED ARGN)
message("defined ARGN")
endif()
foreach(loop_var IN LISTS ARGN)
message(STATUS "loop_var: ${loop_var}")
endforeach()
endmacro()
function(foo)
# 打印ARGN參數值
message(STATUS "ARGN: ${ARGN}")
# 打印ARGC參數值
message(STATUS "ARGC: ${ARGC}")
# 打印ARGV參數值
message(STATUS "ARGV: ${ARGV}")
# 打印ARGV0參數值
message(STATUS "ARGV0: ${ARGV0}")
## 調用宏
bar(x y z)
endfunction()
foo(a b c)
# 打印結束日志
message(STATUS "########## END_TEST_MACRO_VS_FUNCTION\n")
2.4.2 運行結果
-- Selecting Windows SDK version 10.0.18362.0 to target Windows 10.0.17763.
-- The CXX compiler identification is MSVC 19.0.24245.0
-- The C compiler identification is MSVC 19.0.24245.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
--
########## BEGIN_TEST_MACRO_VS_FUNCTION
-- ARGN: a;b;c
-- ARGC: 3
-- ARGV: a;b;c
-- ARGV0: a
defined ARGN
-- loop_var: a
-- loop_var: b
-- loop_var: c
-- ########## END_TEST_MACRO_VS_FUNCTION
-- Configuring done
-- Generating done
-- Build files have been written to: F:/learn_cmake/build
請按任意鍵繼續. . .
2.4.3 小結
我們強調過很多次,在CMake語法中,函數會產生新的作用域,同時會默認生成新的變量即ARGN、ARGC、ARGV等等。
那么,在函數中調用宏時,僅僅只是把宏的實現語句往函數中拷貝了一份(即宏替換),所以,宏中語句的執行都基礎函數變量的基礎上。
因此,也就有示例代碼23行成立的原因,同時也有了對輸出結果第20行的解釋或說明。
2.5 區別4:函數中有一些特有的默認變量
2.5.1 示例代碼(CMakeLists.txt)
cmake_minimum_required(VERSION 3.18)
# 設置工程名稱
set(PROJECT_NAME KAIZEN)
# 設置工程版本號
set(PROJECT_VERSION "1.0.0.10" CACHE STRING "默認版本號")
# 工程定義
project(${PROJECT_NAME}
LANGUAGES CXX C
VERSION ${PROJECT_VERSION}
)
# 打印開始日志
message(STATUS "\n########## BEGIN_TEST_MACRO_VS_FUNCTION")
function(test_func_default_var)
# 打印CMAKE_CURRENT_FUNCTION參數值
message(STATUS "CMAKE_CURRENT_FUNCTION: ${CMAKE_CURRENT_FUNCTION}")
# 打印CMAKE_CURRENT_FUNCTION_LIST_DIR參數值
message(STATUS "CMAKE_CURRENT_FUNCTION_LIST_DIR: ${CMAKE_CURRENT_FUNCTION_LIST_DIR}")
# 打印CMAKE_CURRENT_FUNCTION_LIST_FILE參數值
message(STATUS "CMAKE_CURRENT_FUNCTION_LIST_FILE: ${CMAKE_CURRENT_FUNCTION_LIST_FILE}")
# 打印CMAKE_CURRENT_FUNCTION_LIST_FINE參數值
message(STATUS "CMAKE_CURRENT_FUNCTION_LIST_LINE: ${CMAKE_CURRENT_FUNCTION_LIST_LINE}")
endfunction()
test_func_default_var()
# 打印結束日志
message(STATUS "########## END_TEST_MACRO_VS_FUNCTION\n")
2.5.2 運行結果
-- Selecting Windows SDK version 10.0.18362.0 to target Windows 10.0.17763.
-- The CXX compiler identification is MSVC 19.0.24245.0
-- The C compiler identification is MSVC 19.0.24245.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
--
########## BEGIN_TEST_MACRO_VS_FUNCTION
-- CMAKE_CURRENT_FUNCTION: test_func_default_var
-- CMAKE_CURRENT_FUNCTION_LIST_DIR: F:/learn_cmake
-- CMAKE_CURRENT_FUNCTION_LIST_FILE: F:/learn_cmake/CMakeLists.txt
-- CMAKE_CURRENT_FUNCTION_LIST_LINE: 18
-- ########## END_TEST_MACRO_VS_FUNCTION
-- Configuring done
-- Generating done
-- Build files have been written to: F:/learn_cmake/build
請按任意鍵繼續. . .
2.5.3 小結
- CMAKE_CURRENT_FUNCTION:當前函數名稱
- CMAKE_CURRENT_FUNCTION_LIST_DIR: 當前函數路徑
- CMAKE_CURRENT_FUNCTION_LIST_FILE:當前函數所屬文件
- CMAKE_CURRENT_FUNCTION_LIST_LINE:當前函數定義的起始行數
