环境
os: windows
IDE: iar
toolchain:iar9.32
board: STM32F429
问题描述
同一个float变量,用两行printf打印,先%d打出来,再%.3f打出来,前者输出32(正确),后者打出来是0.000。顺序调换后 .3f也正常了
先打印int,再打印float,输出是32和0.000
先打印float,再打印int,输出是32.123和32 (正确输出)
问题定位
vcvtq_s32_f32 Qd, Qm, 浮点数 ----> 有符号整数或者定点数
printf用%d格式打印val,会使用vcvtq_s32_f32 指令做一次转换。
b1c4地址上的VLDR用于将处于栈顶的val值,放入S0浮点寄存器中
(S0 ~ S31(S)都为32位寄存器,可通过浮点指令或利用符号D0 ~ D15(D代表双字/双精度)成对访问。 例如,S0和S1成对组成D0,而S2和S3则成对组成D1。)
b1c8地址上将字符串地址传入R0
b1ca地址上的VCVT.S32.F32,就是对S0进行浮点数 ----> 有符号整数的转换了,从左到右第一个S0是目的寄存器,这行结束后S0值为32(较大的精度丢失,向零舍入,即将浮点数的小数部分去除,以获取整数部分)
b1ce上的VMOV将S0里的32存入R1,可视作整数存储。
b1d2调用printf
再打印float
红框里的mov没有效果,根据函数调用约定,传入printf的值依然是R0和R1(r0来自[SP],即val,给的R2,但传入printf依然是R0,R1。R1的值依然是b1ce上VMOV后的结果32,用格式%f去输出32,最后输出值为0.000
在X86机器上做了实验,行为也是一样的:
测试代码:
结果:
到这儿,也就很容易看出为什么交换这俩printf的顺序,结果又正确了。
随后我们做了两个尝试:
- 关闭FPU,因为编译出的代码没有使用VCVT做转换,所以结果正确(无论是否交换顺序)
- include stdio.h后,结果也正确(无论是否交换顺序),从而引出了另一个问题
另一个问题
include stdio.h后,不交换printf顺序,输出也正常了,再次看汇编,发现这次printf传参时的参数准备行为变得正常了:
通过VMOV将R0, R1的值传入D0(双精度),r0和r1的值应该是经过__aeabi_f2d之后的返回值(在实际编程中,VFP 中的浮点运算实际是组合使用硬件(执行常见的情况)和软件(处理不常见的情况和导致异常的情况)执行的。__aeabi_f2d即为软件浮点库中的实现)
这里的printf的入参为R0, D0?这里得保持疑问。 TODO:
另外,第二个问题,为什么include stdio后,编译器对printf传参行为也变了。TODO:
参考资料:
- https://www.cnblogs.com/YYPapa/p/6854371.html
- https://blog.csdn.net/sno_guo/article/details/8472809