《CMakeList 知识系统学习系列(三):函数和宏》
函数
一、定义与语法
-
函数(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")# 通过命令调用
二、参数处理机制
- 显示参数
通过形参名直接引用(如 ${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 函数的作用域隔离机制决定了:
- 函数内部默认操作的是当前作用域的变量副本。
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()
- 参数传递:宏支持固定参数和隐式参数(通过
ARGV
、ARGC
、ARGN
访问未命名参数),但需手动处理 。 - 文本替换特性:宏在调用时直接展开代码,类似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() | 可能导致父作用域流程退出 | 仅退出函数逻辑 |
典型场景 | 批量设置编译选项、跨作用域操作 | 模块化逻辑封装、递归设计 |
总结
- 函数:优先用于复杂逻辑,通过作用域隔离保证安全性。
- 宏:谨慎用于简单代码复用,警惕变量泄漏风险。
- 核心原则: 能用函数解决的问题不用宏;必须用宏时,通过局部变量和参数检查降低副作用。