• 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

    懒惰不会让你一下子跌到 但会在不知不觉中减少你的收获; 勤奋也不会让你一夜成功 但会在不知不觉中积累你的成果 越努力,越幸运。
  • 相关阅读:
    C#与独孤九剑
    C#系列视频教程字符和字符串操作
    【设计模式】迪米特法则
    【设计模式】考题 模板方法模式
    C#字符和字符串
    【热门技术】解决Win7 下面很多软件安装不兼容的问题
    C#使电脑发出嗡鸣声
    C#视频教程下载(第一章)
    【设计模式】牛市股票还会亏钱 外观模式
    【设计模式】好菜每回味不同 建造者模式
  • 原文地址:https://www.cnblogs.com/Rainingday/p/6638364.html
Copyright © 2020-2023  润新知