《CMakeList 知识系统学习系列(三):函数和宏》

6 阅读4分钟

《CMakeList 知识系统学习系列(三):函数和宏》

函数

一、定义与语法

  1. 函数(Function)定义

    使用 function()endfunction() 定义,支持参数传递和作用域隔离:

    function(my_func arg1 arg2)
        message("参数1: ${arg1}, 参数2: ${arg2}")
        set(var_in_func "内部变量" PARENT_SCOPE)  # 需显式传递变量到父作用域
    endfunction()
    

参数列表:支持显式参数(如arg1,arg2)和隐式参数(如ARGN,ARGC)

作用域:函数内默认创建新的作用域,变量修改需通过PARENT_SCOPE显式传递到外部

调用方式

  • 函数名不区分大小写,建议统一使用小写 。

  • 支持直接调用或通过 cmake_language(CALL ...) 调用 。

    my_func(10 "Hello")# 直接调用
    cmake_language(CALL my_func 10 "Hello")# 通过命令调用
    

二、参数处理机制

  1. 显示参数

通过形参名直接引用(如 ${arg1}

示例

#函数定义
function(helloworld arg1)
    message(STATUS "Hello, ${arg1}")
endfunction()

#函数调用
helloworld("world")
HELLOWORLD("world2")
cmake_language(CALL helloworld "world3")
输出
-- Hello, world
-- Hello, world2
-- Hello, world3

2. 可变参数

  • ARGC:参数总数量。
  • ARGV:所有参数的列表。
  • ARGN:超出形参数量的额外参数列表。
#函数定义
function(helloworld arg1)
    message(STATUS "Hello, ${arg1}")
    message("所有参数个数:${ARGC}")
    message("所有参数列表:${ARGV}")
    message("超出参数列表:${ARGN}")
    # 通过foreach遍历参数列表
    foreach(arg IN LISTS ARGN)
        message(STATUS "List item: ${arg}")
    endforeach()
endfunction()

#函数调用
helloworld("world" 1 2 3)
输出
-- Hello, world
所有参数个数:4
所有参数列表:world;1;2;3
超出参数列表:1;2;3
-- List item: 1
-- List item: 2
-- List item: 3

**参数传递注意事项:**若形参为变量名,需通过 ${${var}} 获取实际值:

set(age 10)
function(show_var var_name)
    message(STATUS "${var_name} is ${${var_name}}")
endfunction()
show_var(age)

输出:
-- age is 10

传递变量参数最好用${var}。不然方法内部得做区分处理

三、作用域与变量传递

function里面使用set.只对function里面的变量生效。如果想要传递给外部,则需添加PARENT_SCOPE参数。

示例如下:

#作用域
set(age 10)
function(set_var)
    # 设置只在函数内部生效的变量
    set(age 20)
    message(STATUS "function age:${age}")
endfunction()
set_var()
输出
-- function age:20
-- age:10

可以看到,在方法内部修改变量,只在内部有效

如果想要全局变量有效。需添加PARENT_SCOPE 参数

#作用域
set(age 10)
function(set_var)
    # 设置只在函数外部生效的变量
    set(age 20 PARENT_SCOPE)
    message(STATUS "function age:${age}")
endfunction()
set_var()
message(STATUS "age:${age}")

输出:
-- function age:10
-- age:20

可以看到function内部的age又不变了。

至此我们可以得知CMake函数作用于规则: CMake的函数在调用时会创建一个新的作用域,函数内部对变量的修改默认只在这个新作用域内有效,除非显式地使用PARENT_SCOPE来影响父作用域。也就是说,函数内部的变量访问和修改默认是隔离的。

CMake 函数的作用域隔离机制决定了:

  1. 函数内部默认操作的是当前作用域的变量副本。
  2. PARENT_SCOPE 仅影响父作用域,不自动同步到当前作用域。

既要函数内部修改,外部也要修改。那就调用两次set

#作用域
set(age 10)
function(set_var)
    # 设置函数外部生效的变量
    set(age 20 PARENT_SCOPE)
    # 设置只在函数内部生效的变量
    set(age 20)
    message(STATUS "function age:${age}")
endfunction()
set_var()
message(STATUS "age:${age}")

输出:
-- function age:20
-- age:20

宏(Macro)

1. 宏的定义与基础语法

在CMake中,宏通过 macro()endmacro() 定义,语法如下:

macro(宏名 参数1 参数2 ...)
# 宏体
endmacro()
  • 参数传递:宏支持固定参数和隐式参数(通过 ARGVARGCARGN 访问未命名参数),但需手动处理 。
  • 文本替换特性:宏在调用时直接展开代码,类似C语言的宏替换,而非函数的作用域隔离 。
#宏
macro(hello arg)
    message(STATUS "Hello, ${arg}")
endmacro()
hello("world")

输出
-- Hello, world

2 宏的变量泄漏风险强调

  • 问题:需明确宏的变量修改会直接影响父作用域。

宏内部变量默认在父作用域可见。set变量修改的就是全局

#宏
macro(hello)
    set(age 20)
    message(STATUS "macro, ${age}")
endmacro()
hello()
message(STATUS "age, ${age}")

输出
-- macro, 20
-- age, 20

3. 宏的实际应用场景

macro(set_macro_var)
    set(var "宏内修改")
endmacro()
set(var "初始值")
set_macro_var()
message("外部变量: ${var}")  # 输出:宏内修改

场景1:跨目标统一配置

Cmake
macro(add_network_lib target)
    add_library(${target} ${ARGN})
    target_compile_definitions(${target} PRIVATE ENABLE_SSL)
    target_link_libraries(${target} openssl)
endmacro()
add_network_lib(server src/server.cpp)# 复用配置

场景2:条件编译控制

Cmake
macro(enable_feature feature_name)
    if(${feature_name}_ENABLED)
        add_compile_definitions(USE_${feature_name})
    endif()
endmacro()
enable_feature(GPU)# 根据变量GPU_ENABLED动态定义宏

场景3:第三方库宏冲突解决 通过优先包含覆盖头文件:

macro(override_thirdparty_macro)
    target_include_directories(app
        BEFORE# 关键指令
        PRIVATE overrides/# 自定义宏头文件
        ${THIRDPARTY_INCLUDES}
    )
endmacro()

4. 宏定义的高级技巧

  • 参数解析:结合 cmake_parse_arguments 处理命名参数 :

    macro(complex_macro)
        set(options VERBOSE)
        set(oneValueArgs OUTPUT)
        cmake_parse_arguments(MACRO_ARGS "${options}" "${oneValueArgs}" "" ${ARGN})
    # 使用MACRO_ARGS_VERBOSE和MACRO_ARGS_OUTPUT
    endmacro()
    
  • 生成器表达式:在宏中嵌入条件编译逻辑 :

    macro(set_debug_flag target)
        target_compile_definitions(${target}
            $<$<CONFIG:Debug>:DEBUG_MODE=1>
        )
    endmacro()
    

宏与函数的对比

特性宏(Macro)函数(Function)
作用域直接展开,影响父作用域变量创建子作用域,需显式传递变量
参数处理通过ARGV/ARGC手动处理自动生成ARGV/ARGC/ARGN变量
性能轻量级,适合简单代码复用适合复杂逻辑,避免代码膨胀
return()可能导致父作用域流程退出仅退出函数逻辑
典型场景批量设置编译选项、跨作用域操作模块化逻辑封装、递归设计

总结

  • 函数:优先用于复杂逻辑,通过作用域隔离保证安全性。
  • :谨慎用于简单代码复用,警惕变量泄漏风险。
  • 核心原则: 能用函数解决的问题不用宏;必须用宏时,通过局部变量和参数检查降低副作用。