浮点数比较陷阱
前提:
- 我维护的老代码, 没有文件系统, 持久性变量被存在了 flash 的固定位置
- 一个结构体里面存了好几个浮点数, float 型
- 读出该结构体之后会对结构体进行数据矫正, 将异常值修改成默认值
- 在对 MCU flash 全片擦除, 烧录之后运行会导致死机, 定位原因发现有个 float 类型的 field 不会被正确矫正, 导致调用字库的时候溢出, 进入 hardfault 中断从而出现死机.
分析:
问题对比
if (sys_parms.fm_now_fre < 87.5 || sys_parms.fm_now_fre > 108.5) {
sys_parms.fm_now_fre = 87.5;
}
这个 field fm_now_fre 被成功矫正成了 87.5
if (sys_parms.sys_vol > 60.0 || sys_parms.sys_vol < 0.0) {
sys_parms.sys_vol = 20.0;
}
这个filed sys_vol 没有被成功矫正, 经过调试器观察为 -nan
原因
- flash 在被擦除之后是全0xff, 所以 float 类型的值就是 -nan
- 编译器优化了浮点数的比较过程, 在比较非零正数的时候会把数值转换成一个整型, 但是在对比 0 的时候因为零的浮点数规则特殊是使用浮点运算器运算的, 不是直接由cpu计算, 浮点数和 nan 比较的时候就是 false 所以这个
sys_vol就没有得到矫正, 导致了数组溢出的问题.
汇编展示
float 值和非零正数比较汇编
0x08012B7E LDR r1, [r10, #0x00] ; 加载 FM_Now_Fre 的原始位模式 (0xFFFFFFFF)
0x08012B82 ADDS r2, r1, r0 ; 整数加法!(r0 是一个偏移量常量)
0x08012B84 MOV r1, #0x2A0000 ; 设定一个整数阈值
0x08012B88 CMP r2, r1 ; 整数比较!
0x08012B8A BLS 0x08012B94 ; 如果小于等于则跳转(跳过修正逻辑)
float 值和零比较汇编
0x08012D0C EEB50AC0 VCMPE.F32 s0,#0.0 ; 真正的 FPU 比较指令!判断是否 < 0.0
0x08012D10 EEF1FA10 VMRS APSR_nzcv,FPSCR ; 关键:把 FPU 的状态位搬到 CPU 状态寄存器
0x08012D14 D201 BCS 0x08012D1A ; 如果 Carry 位被置 1,跳转到 0x08012D1A (跳过赋值)

浙公网安备 33010602011771号