**
关于C语言算术运算时类型提升的坑
**
起因
在stm32写延时函数时,使用16位定时器TIM6做延时函数,代码如下:.
// 延时函数
void DelayUs(uint16_t nUs)
{
uint16_t tickStart = TIM6->CNT;
/* 延时等待 */
while (TIM6->CNT - tickStart < nUs);
}
原理就是不停的读TIM6定时器的计数值和初始值作差和要延时的值作比较。
但是实际测试时,发现程序会死在while循环里出不来,怀疑是优化问题或者是无符号变量相减问题。
问题分析
由于看不懂ARM的汇编指令,开始没有对汇编代码仔细分析。
起初怀疑是代码被编译器优化了。所以做了如下测试:
- tickStart改用volatile修饰,但是发现仍不起作用;
- 尝试将优化等级调整为-O0,仍不行;
- while循环增加循环主体,在循环里翻转IO,发现一直翻转,确实跳不出循环;
- 尝试利用中间变量,测试功能恢复正常,代码修改如下;
// 延时函数
void DelayUs(uint16_t nUs)
{
uint16_t tickStart = TIM6->CNT;
uint16_t value=TIM6->CNT - tickStart;
/* 延时等待 */
while (value < nUs){value=TIM6->CNT - tickStart;};
}
- 既然中间变量可以,那么考虑可能为无符号类型相关,直接强制类型转换,测试功能正常,代码如下
// 延时函数
void DelayUs(uint16_t nUs)
{
uint16_t tickStart = TIM6->CNT;
/* 延时等待 */
while ((uint16_t)(TIM6->CNT - tickStart) < nUs);
}
类似代码验证
void test(void)
{
uint8_t a=10;
uint8_t b=20;
if(a-b>1)
printf("1\n");
else
printf("2\n");
}
在keil和DEV C++中验证,输出均为2。
在deepseek和百度文心一言中测试,输出均为1。
明显AI是错的
原因分析
直接将正确结果告诉AI,AI分析结果如下:
关键点:整数提升和类型转换
在C语言中,当进行算术运算时,较小的整数类型(如 uint8_t)会被提升为 int 类型(除非 int 无法表示所有值,这种情况下会提升为 unsigned int)。这是“整数提升”(integer promotion)的规则。
表达式 a - b 的实际计算
整数提升:a 和 b 都是 uint8_t 类型(8位无符号整数)。
在进行 a - b 时,它们会被提升为 int 类型(假设 int 是32位,这是常见的情况)。
因此,a 提升为 10(int),b 提升为 20(int)。
a - b 计算为 10 - 20 = -10(int 类型)。
比较 -10 > 1:-10 是负数,显然不大于 1,因此条件为假。
因此,else 分支被执行,输出 2。
也就是说,编译器将比变量宽度小于Int类型的数据均提升到Int类型计算,无符号变成了有符号的数。
验证
uint16_t a=0x0ff0;
uint16_t b=0x0fff;
if(a-b>1)
printf("1\r\n");
else
printf("2\r\n");
结果仍为2
说明Uint16_t也被类型提升成了int类型的有符号数了。
uint32_t a=0x0ff0;
uint32_t b=0x0fff;
if(a-b>1)
printf("1\r\n");
else
printf("2\r\n");
将类型改为uint32_t,输出结果为1
说明类型没有被提升。
到此,验证正确
汇编源码分析
这是uint16_t的汇编代码
这是uitnt32_t的汇编代码
比较两者代码,发现只有一处不同,就是0x08000f04处
uint16使用的是BLE指令,uint32_t处使用的是BLS指令。
求助AI,介绍如下:
在ARM汇编中,BLE和BLS是两种不同的条件分支指令,它们的主要区别体现在应用场景、比较类型及条件判断依据上,以下是具体分析:
应用场景
BLE:通常用于实现有符号数的比较和分支,在需要判断有符号数是否小于或等于时使用。
BLS:通常用于实现无符号数的比较和分支,在需要判断无符号数是否小于或等于时使用。
比较类型
BLE:涉及有符号数的比较,会考虑数值的正负和大小。
BLS:涉及无符号数的比较,只考虑数值的大小,不考虑正负。
条件判断依据
BLE:条件分支指令,当上一次运算结果为有符号数小于等于时分支。
BLS:条件分支指令,当上一次运算结果为无符号数小于等于时分支。
确实如之前分析的那样:
uint16_t/uint8_t相减是按有符号处理的,uint32_t是按无符号处理的。
使用时千万小心。
总结
类型提升会导致数据类型发生变化,无符号的数会变成有符号的数。
该问题主要是出现在无符号数据相减时,常见于时间戳判断上,时间戳溢出导致该坑。
解决办法:
- 相减后的结果强制类型转换
- 使用更大类型的数据,保证编译器不会提升数据类型
- 直接代码中判断是否溢出(稍微啰嗦一点)
2092

被折叠的 条评论
为什么被折叠?



