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都不是真实的变量。
那么,示例程序中第6167行,输出第3134行怎么来的呢?宏替换的作用。如果不理解,需要回去恶补一下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:当前函数定义的起始行数