AVR-GCC宏定义运算行为相关技术咨询
看起来你在调试AVR-GCC环境下宏定义参与数值运算的问题对吧?从你简化后的代码和描述来看,大概率是16位整数溢出导致的计算结果不符合预期,毕竟AVR平台的int默认是16位有符号类型,最大值只有32767,很容易在多步乘法运算中触发溢出。
先把你贴的简化代码放出来方便分析:
#include <stdint.h> #define WHEELS_PWM_CYCLES_PER_MS (5) #define WHEELS_TIME_TO_GO_1_CM_MS (10) int main(void) { for (;;) { uint32_t x = (uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * 100 * (uint32_t)WHEELS_PWM_CYCLES_PER_MS * 100 / 50; } return 0; }
你提到这是从控制直流电机PWM的完整程序里精简出来的,核心困扰应该是这个宏参与的运算结果不对,导致电机控制逻辑出问题对吧?
这里的关键坑点在于:AVR-GCC的宏替换是预处理器阶段完成的,替换后的数值默认会以int类型参与运算。我们来算一下你表达式里的中间值:如果没有提前做强制类型转换,10 * 100 * 5 * 100的结果是500000,这个数值远远超过了16位int的最大值32767,会触发有符号整数溢出——这在C标准里属于未定义行为,最终得到的结果肯定和预期不符。
你现在的代码里虽然加了(uint32_t)强制转换,但要注意转换的时机:你把两个宏的值都转成了uint32_t,乘法运算从左到右执行时,第一个(uint32_t)10 * 100会以32位无符号整数计算,结果是1000(32位),后续的乘法和除法都会自动以32位类型进行,这样就不会出现溢出问题,最终结果应该是10000,是符合预期的。
那如果你之前的完整程序里没有给宏加这个强制类型转换,比如直接写WHEELS_TIME_TO_GO_1_CM_MS * 100 * WHEELS_PWM_CYCLES_PER_MS * 100 / 50,那所有运算都会以16位int执行,中间值500000溢出后就会得到错误的结果,进而影响电机的PWM控制逻辑。
另外还要夸你一句:定义宏的时候给宏体加了括号(比如#define WHEELS_PWM_CYCLES_PER_MS (5)),这个习惯非常好,能避免宏在复杂表达式里因为运算符优先级出问题,很多新手都容易忽略这点。
最后给你两个优化建议,从根源解决这类问题:
- 要么在宏定义时就指定为32位常量,比如
#define WHEELS_PWM_CYCLES_PER_MS ((uint32_t)5),这样所有参与运算的数值默认都是32位,从一开始就杜绝溢出的可能; - 要么在表达式的最开头就把第一个操作数强制转成
uint32_t,因为乘法运算中只要有一个操作数是32位,其他操作数会自动提升为32位类型,比如(uint32_t)WHEELS_TIME_TO_GO_1_CM_MS * 100 * WHEELS_PWM_CYCLES_PER_MS * 100 / 50,这样也能保证全程32位运算。
你可以试试这两种方法,应该就能解决你之前电机控制程序里的问题了。
备注:内容来源于stack exchange,提问作者user2908112

