1. 问题描述:
当使用uocs printf(),sprintf()打印浮点数问题会出问题,但是裸机不会出问题
我现在使用STM32跑UCOS,在使用sprintf打印float类型时候,不管是何值最后都是0.0,但是类型是int,short类型时没有问题。网上查到是任务堆栈8字节对齐就可以了。
当没有操作系统时,系统堆栈是8字节对齐的,但是当使用ucos时,用户任务不一定是8字节对齐.
Task1-LED1 中的堆栈起始指针0x200004A4,不是8字节对齐,所以但在Task-LED1任务中调用printf等系列函数就会出现问题.
2. 解决方法
我用的是IAR,通过#pragma data_alignment指定对齐字节数
#pragma data_alignment=8
OS_STK Task1_LED1_Stk[Task1_LED1_Stk_Size];
#pragma data_alignment=8
OS_STK Task2_backlight_Stk[Task2_backlight_Stk_Size];
3. 8字节对齐原因
这事儿的历史在于ARM本身不支持非对齐数据存取;因此在有了64Bit的数据操作指令后,指令要求8字节对齐。进而,在编译器的某个版本之后(RVCT3?),AAPCS就要求堆栈8字节对齐。
是先有8字节对齐的AAPCS,然后才有的CM3。先后顺序要注意。CM3 r2p0之前,自动压栈也不要求8对齐,r2p0好像才是强制对齐的。
printf的8对齐是C运行库要求的,和硬件无关,C RTL手册有写,可以去阅读。其根源在于AAPCS要求;而AAPCS根源在于LDRD这类指令。
换句话,未来如果128Bit数据操作有了,ARM还不支持非对其,那AAPCS可能升级为16字节对齐。
供参考,CM3和C-RTL对齐的问题。
最近在CSDN上看到一个网友写下了类似如下代码,想以小数格式输出一个整数:
int
a
=
0
, b
=
0
;
printf(
"
%f, %d
"
, a, b);
可是运行结果并不尽如人意,%f字段输出了0,%d字段输出一个较大的数据。
printf格式串中的%f到底是float还是double?
因为我最近刚阅读了浮点数的内存表示方法,所以对上述代码做出解释如下:
%f为double类型,需要两个字节表示,所以,printf在遇到%f时即将a,b的两个整型数据都读了去,而到了需要输出%d的时候,只能读取b的下一个单元,自然不是所期望的数据了。
但是有朋友说%f是float类型,%lf才是double类型,具此我特意查阅了MSDN和Linux man手册,均没有发现此类描述,在linux man手册中,说明%lf为long double类型。
为了说明问题,我又做了几个实验:
实验一,检查%f需要读取几个字节
int
a
=
0
, b
=
0
, c
=
5
;
printf(
"
%f,%d\n
"
, a, b, c);
输出结果:
0,5
结论:%f读取8个字节,即两个整型大小
实验二,检查%lf需要读取几个字节
int
a
=
0
, b
=
0
, c
=
5
;
printf(
"
%lf,%d\n
"
, a, b, c);
输出结果:
0,5
结论:%lf也读取8个字节(也许和机器位宽有关,我是32位的机器)
实验三,检查printf读取float类型数据
float
a
=
0.0f
;
int
b
=
5
;
printf(
"
%f,%d\n
"
, a, b);
输出结果:
0.0,5
结论:float类型只占4个字节的数据,但前面实验一已经证明%f会读8个字节,即double类型的宽度,所以,编译器在将float类型参数入栈的时候,事先转换成了double类型。
实验四,再次证明实验三的结论
float
a
=
0.0f
;
int
b
=
5
;
printf(
"
%d,%d,%d\n
"
, a, b);
输出结果:
0,0,5
结论:a在入栈的时候,占了8个字节。
以上4步,我觉得可以证明
%f是按double类型输出的了
,另外,我也知道了float类型在作为参数进行传递的时候,编译器会先将它转换成double类型。