第26章:数学运算:math()命令进行简单的算术运算
说实话,很多刚接触CMake的朋友都会问一个问题:CMake里能不能做数学运算?
答案是肯定的。而且CMake还专门提供了一个math()命令,专门用来处理简单的算术运算。我个人觉得,这个命令虽然功能不算强大,但在某些场景下真的能帮大忙。
为什么需要math()?
你想想看,在构建过程中,我们经常需要处理一些数值。比如版本号的自增、数组索引的计算、或者根据某个条件动态调整编译参数。这些场景下,如果CMake没有数学运算能力,我们就得靠外部脚本或者硬编码,那多麻烦。
我在项目中遇到过这样一个需求:需要根据CPU核心数动态调整并行编译的任务数。这时候math()就派上用场了。
math()的基本语法
先看个最简单的例子:
math(EXPR result "1 + 2")
message("1 + 2 = ${result}")
输出结果:1 + 2 = 3
语法结构其实很简单:
math(EXPR <输出变量> "<算术表达式>")
嗯,这里要注意几点:
EXPR是固定关键字,告诉CMake我们要做表达式计算- 表达式必须用双引号括起来
- 结果会存到第一个参数指定的变量中
支持的运算符
CMake的math()支持以下运算符:
| 运算符 | 说明 | 示例 |
|---|---|---|
+ |
加法 | math(EXPR r "5 + 3") |
- |
减法 | math(EXPR r "10 - 4") |
* |
乘法 | math(EXPR r "6 * 7") |
/ |
除法(整数除法) | math(EXPR r "15 / 4") |
% |
取模 | math(EXPR r "17 % 5") |
| |
按位或 | math(EXPR r "0x0F | 0xF0") |
& |
按位与 | math(EXPR r "0xFF & 0x0F") |
^ |
按位异或 | math(EXPR r "0xAA ^ 0x55") |
~ |
按位取反 | math(EXPR r "~0xFF") |
<< |
左移 | math(EXPR r "1 << 8") |
>> |
右移 | math(EXPR r "256 >> 4") |
注意:CMake的除法是整数除法,不会产生小数。比如
15 / 4的结果是3,而不是3.75。这一点和C语言的整数除法行为一致。
实际应用场景
场景一:版本号自增
我在做持续集成的时候,经常需要自动递增版本号。用math()可以轻松实现:
set(VERSION_MAJOR 1)
set(VERSION_MINOR 5)
set(VERSION_PATCH 12)
# 打补丁版本自增
math(EXPR VERSION_PATCH "${VERSION_PATCH} + 1")
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
message("新版本号: ${VERSION_STRING}")
场景二:动态计算编译参数
我记得有一次,项目需要根据目标平台的内存大小来调整缓冲区大小。虽然CMake不能直接获取内存信息,但我们可以通过外部脚本传入参数:
# 假设从外部获取了内存大小(单位MB)
set(MEMORY_SIZE 4096)
# 计算缓冲区大小:内存的1/4
math(EXPR BUFFER_SIZE "${MEMORY_SIZE} / 4")
add_definitions(-DBUFFER_SIZE=${BUFFER_SIZE})
场景三:位运算处理标志位
有些项目会使用位标志来控制编译选项。比如:
# 定义标志位
set(FLAG_A 1) # 0x01
set(FLAG_B 2) # 0x02
set(FLAG_C 4) # 0x04
# 组合标志
math(EXPR COMBINED "${FLAG_A} | ${FLAG_B} | ${FLAG_C}")
message("组合标志值: ${COMBINED}") # 输出7
# 检查某个标志是否设置
math(EXPR CHECK "${COMBINED} & ${FLAG_B}")
if(CHECK)
message("FLAG_B 已设置")
endif()
使用变量进行运算
在实际项目中,我们很少直接写死数值,更多是使用变量。看这个例子:
set(A 10)
set(B 20)
math(EXPR SUM "${A} + ${B}")
math(EXPR DIFF "${B} - ${A}")
math(EXPR PROD "${A} * ${B}")
math(EXPR QUOT "${B} / ${A}")
message("和: ${SUM}")
message("差: ${DIFF}")
message("积: ${PROD}")
message("商: ${QUOT}")
小技巧:变量名在表达式中要用
${}展开。如果你忘了加${},CMake会把它当成普通字符串处理,结果就会出错。我曾经因为这个bug排查了半天...
知识结构图
下面这张图总结了math()命令的核心知识体系:
避坑指南
我曾经踩过几个坑,分享出来给大家提个醒:
坑1:除法精度问题
前面说了,CMake的除法是整数除法。如果你需要小数运算,那就得另想办法了。我一般会用外部脚本(比如Python)处理完再传回CMake。
前面说了,CMake的除法是整数除法。如果你需要小数运算,那就得另想办法了。我一般会用外部脚本(比如Python)处理完再传回CMake。
坑2:变量未定义
如果表达式中用到的变量没有定义,CMake会把它当成0处理。这有时候会导致难以发现的bug。我建议在使用前先用
如果表达式中用到的变量没有定义,CMake会把它当成0处理。这有时候会导致难以发现的bug。我建议在使用前先用
if(DEFINED ...)检查一下。
坑3:括号的使用
CMake的
CMake的
math()支持括号改变运算优先级。比如math(EXPR r "(1 + 2) * 3")结果是9。但要注意,括号必须成对出现,否则会报错。
总结
math()命令虽然功能简单,但在CMake的日常使用中非常实用。它支持基本的算术运算和位运算,可以满足大部分构建过程中的数值计算需求。
我个人建议,如果你需要更复杂的数学运算(比如三角函数、对数等),还是用Python脚本处理完再传给CMake比较靠谱。毕竟CMake不是万能的,它只是一个构建工具。
好了,这一章就到这里。记住:能用math()解决的问题,就别去折腾外部脚本了。