第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()命令的核心知识体系:

math(EXPR ...) 基本语法结构 支持的运算符 + - * / % | & ^ ~ << >> 算术运算符 | 位运算符 | 移位运算符 典型应用场景 版本号自增 动态计算参数 位标志处理

避坑指南

我曾经踩过几个坑,分享出来给大家提个醒:

坑1:除法精度问题
前面说了,CMake的除法是整数除法。如果你需要小数运算,那就得另想办法了。我一般会用外部脚本(比如Python)处理完再传回CMake。
坑2:变量未定义
如果表达式中用到的变量没有定义,CMake会把它当成0处理。这有时候会导致难以发现的bug。我建议在使用前先用if(DEFINED ...)检查一下。
坑3:括号的使用
CMake的math()支持括号改变运算优先级。比如math(EXPR r "(1 + 2) * 3")结果是9。但要注意,括号必须成对出现,否则会报错。

总结

math()命令虽然功能简单,但在CMake的日常使用中非常实用。它支持基本的算术运算和位运算,可以满足大部分构建过程中的数值计算需求。

我个人建议,如果你需要更复杂的数学运算(比如三角函数、对数等),还是用Python脚本处理完再传给CMake比较靠谱。毕竟CMake不是万能的,它只是一个构建工具。

好了,这一章就到这里。记住:能用math()解决的问题,就别去折腾外部脚本了。


公众号:蓝海资料掘金营,微信deep3321