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:當前函數定義的起始行數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM