CMake语法—宏和函数(macro vs function)

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:当前函数定义的起始行数
posted @ 2021-11-30 09:50  kaizenly  阅读(4478)  评论(3编辑  收藏  举报
打赏