Sprintf格式化float型引发的问题!

项目需要打印一串浮点型数字,于是刚好用sprintf函数格式化,然后出现了意想不到的问题,float型数字全是0.00.。而后面的数字也出现错误。网上查找原因,才发现没有遵循AAPCS栈使用规约,看了之后发现之前写程序真是在冒险。

规约规定,栈任何时候都得4字节对齐,在调用入口得8字节对齐。在这个约定里,栈的4字节对齐确实得任何时候都遵守,而且你想不遵守都难,因为SP的最后两位是硬件上保持0的。而对于8字节对齐,这就需要码农和编译器配合着来。需要说明的一点是,8字节对齐即使不遵守,一些情况下也没问题,只要主调和被调用例程两边把堆栈使用,传参,返回等处理好就行,也就是说两边有自己的一套约定就行。但是有时候,主调这边在调用严格遵守AAPCS的函数时,没有将栈保持在8字节对齐上,那就会出问题。

贴两张图可以看到字节对齐后的效果,图片截取自.map文件

__align(8) char tcp_server_tramsvbuf[TCP_SERVER_TX_BUFSIZE];//8字节对齐

__align(4) char tcp_server_tramsvbuf[TCP_SERVER_TX_BUFSIZE]; //4字节对齐

char tcp_server_tramsvbuf[TCP_SERVER_TX_BUFSIZE];

对于一个char型的数组,栈顶在任意位置都可以,但是当它涉及到sprintf输出一个浮点数时,就可能出错。AAPCS规则要求堆栈保持8字节对齐。如果不对齐,调用一般的函数也是没问题的。但是当调用需要严格遵守AAPCS规则的函数时可能会出错。
例如调用sprintf输出一个浮点数时,栈必须是8字节对齐的,否则结果可能会出错。

什么是栈对齐?栈的字节对齐,实际是指栈顶指针须是某字节的整数倍。

字节对齐跟数据在内存中的位置有关,如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。

编程时,对于AAPCS栈使用约定的遵守,总的来说就两条:

1. 汇编文件中需要我们亲自动手来保证遵守AAPCS栈使用约定。

(特别注意每次从汇编进入C的世界时,要保证汇编部分的编码在调用c接口时栈是8字节对齐的,不要疏忽了,因为c编译器可不负责调整。c编译器说你得送给我的SP就是8字节对齐的,我才能保证接下来的C部分没有结束之前,遵守AAPCS栈使用约定)

2. 在C文件中,由编译器来处理。

CORTEX-M3 中断控制器的栈对齐调整功能(该功能在r2p0版本以后的内核中均默认开启,STKALIGN位默认为1)

Cortex M3 NVIC CCR寄存器(控制与配置寄存器)的STKALIGN位置1,那么在发生中断时,进入中断响应函数前,内核会首先检查当前正在使用的栈指针是否8字节对齐,如果是,则正常将xPSR,PC,LR,SP,R0-R3入栈,如果不是,则先把SP-4,调整为8字节对齐,然后将xPSR第九位置1,接着把xPSR,PC,LR,SP,R0-R3入栈,再然后才进入中断响应函数。这样可以保证程序在运行过程中,如果在栈没有发生4字节对齐的地方发生中断了,进入到中断响应函数的时候也是遵守AAPCS栈使用约定的。如果中断服务程序是做任务切换的,那么前面的情况就是将任务栈调整为对齐,然后进入异常服务程序后使用系统栈,那如果系统栈本来就是不对齐的呢?通过中断来做任务切换的情况下,中断控制器并不会对系统栈进行调整,怎么办?其实这也不用担心,以μC/OS-II为例,在cortex-m3上通常使用PendSV异常来做任务切换,即将OSCtxSw以及OSIntCtxSw都设为仅完成PendSV异常触发功能,然后在PendSV异常服务程序中进行任务切换。由于上电时刻系统处于特权级模式,只要我们保证从上电开始到第一次系统调用,使用的栈都是系统栈MSP就可以了,这样即使第一次要进入任务切换时MSP不对齐,中断向量控制器也会给调整为8字节对齐状态,虽然这个第一次任务切换后除了中断再也不会使用MSP,但只要我们同时保证所有汇编部分都不会破坏8字节对齐规约,那么从此以后MSP都会是8字节对齐的。

 __align(num) 和__packed 

 __align(num)这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时  就要用到此命令__align(8)进行修饰限制,来保证数据对象是相应对齐。这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节   对齐,但是不能让4字节的对象2字节对齐。  __align是存储类修改,他只修饰最高级类型对象,不能用于结构或者函数对象。

__packed是进行一字节对齐,用于C语言结构的压缩,没有填充和对齐。  
1.不能对packed的对象进行对齐  
2.所有对象的读写访问都进行非对齐访问  
3.float及包含float的结构联合及未用__packed的对象将不能字节对齐  
4.__packed对局部整形变量无影响  
5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定  
  义为packed。  

在 Cotex-M3 programming manual 中有提到对齐问题

 1.通常编译器在生成代码的时候都会进行结构体填充,保证(结构体内部成员)最高性能的对齐方式。
  2.编译器自动分配出来结构体的内存(比如定义为全局变量或局部变量)肯定是对齐的。
  3.查阅帮助文档的malloc部分,mdk的标准malloc申请的内存区时8字节对齐的。
  4.若自定义的malloc函数本身没有对分配的内存实现4字节或以上的对齐操作,分配出来的不对齐的内存,编译器是不知道的,所以很可能会产生问题。
     此时最好的解决方式在内存池数组前添加__align(4)关键字,只需保证自定义malloc分配出来的首地址是4字节对齐。
     比如:__align(4) u8 mem1base[MEM1_MAX_SIZE];

 参考:http://www.cnblogs.com/King-Gentleman/p/5940480.html

       http://www.cnblogs.com/reload/p/3159053.html

posted @ 2017-03-29 10:28  不明白就去明白  阅读(1939)  评论(0编辑  收藏  举报