0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在堆嵌入式单片机编程中为什么大多时候要保证堆栈8字节对齐呢?

STM32嵌入式开发 来源:CSDN 2023-08-28 10:48 次阅读

本文主要介绍了嵌入式单片机编程中,为什么大多时候要保证堆栈8字节对齐的问题。

字节对齐原则

1. 结构(struct)(或联合(union)) 中的第一个数据成员放在 offset 为 0 的地方,以后每个数据成员存储的起始位置,要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如 int 型变量在 32 位编译环境下为 4 字节,则要从 4 的整数倍地址开始存储);

2. 如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.

如:struct a 里存有 struct b, b 里有 char, int , double 等元素,那 b 应该从 8 的整数倍开始存储.;

3. 结构体的总大小,也就是 sizeof 的结果,必须是其内部最大成员的整数倍,不足的要补齐;

dd396a9e-433e-11ee-a2ef-92fbcf53809c.png

总大小为最大成员变量大小的整数倍,sizeof(AA) = 24;sizeof(BB) = 32。

#pragma pack():

在代码前加一句 #pragma pack(1),会发现 sizeof(AA) = 17;sizeof(BB) = 24;

AA 是 8+4+1+4=17;

BB 是 5+4+8+4+2+1=24;

这就是理想中的没有内存对齐的情况,所以 #pragma pack(1) 是告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则。

即#pragma pack(n)就是所有的对齐都按照n的整数倍对齐。

Vc,Vs等编译器默认是 #pragma pack(8),所以测试我们的规则会正常;

gcc 默认是 #pragma pack(4),并且 gcc 只支持 1, 2, 4 对齐。套用三原则里计算的对齐值是不能大于 #pragma pack 指定的n值。

为什么要保证堆栈8字节对齐

AAPCS 规则要求堆栈保持 8 字节对齐。如果不对齐,调用一般的函数也是没问题的。但是当调用需要严格遵守 AAPCS 规则的函数时可能会出错。

例如调用 sprintf 输出一个浮点数时,栈必须是 8 字节对齐的,否则结果可能会出错。

实验验证:

dd545f52-433e-11ee-a2ef-92fbcf53809c.png


1.在 A 处设置断点,让程序全速运行至 A

2.在 MDK 中修改 MSP 的值使 MSP 满足 8 字节对齐

3.全速运行程序,观察 buf 中的字符为 1.234 结果正确

4.回到第 2 步,修改 MSP 使之只满足 4 字节对齐而不满足 8 字节对齐

5.全速运行程序,观察 buf 中的字符为 -2.000 结果错误

该实验证明了调用 sprintf 输出一个浮点数必须要保证栈 8 字节对齐。

编译器为我们做了什么

先看一个实验:

dd672d62-433e-11ee-a2ef-92fbcf53809c.png


保证初始的时候堆栈是 8 字节对齐的;

1.在 A 处设置断点;

2.全速运行至 A,观察 MSP=0x2000025c,没有 8 字节对齐;

3.略微修改一下 main 函数代码如下,其他部分代码不变;

dd810d90-433e-11ee-a2ef-92fbcf53809c.png


4.同样在 A 处设置断点;

5.全速运行至 A,观察 MSP=0x200002d8,这次 8 字节对齐了; 这个实验说明了如果编译器发现了某个函数需要调用浮点库时会自动调整编译生成的汇编代码,从而保证调用这些浮点库函数时堆栈是8字节对齐的。

换句话说如果我们保证了栈初始的时候是8字节对齐的,那么编译器可以保证以后调用浮点库时堆栈仍是8字节对齐的。

os下应该怎样设置任务堆栈

由上面的讨论可知给任务分配栈时需要保证栈是 8 字节对齐的,不然在该任务中凡是调用 sprintf 的函数均会出错,因为栈一开始就是不对齐的。

是否保证了栈初始是8字节对齐了就万事大吉了呢。no!大家请看一种特殊的情况:

dd92f834-433e-11ee-a2ef-92fbcf53809c.png

mian函数的反汇编如下:

ddad50d0-433e-11ee-a2ef-92fbcf53809c.png



保证初始的时候堆栈是 8 字节对齐的;

1.在 A 处设置断点;

2.全速运行至 A,观察此时 MSP=0x200002e4 未对齐;

3.在 MDK 中将 SVC 的挂起位置 1;

4.在 B 处设置断点;

5.全速运行至 B,观察此时 MSP=0x200002b4 未对齐;

6.继续全速执行,观察 buf 中的字符为: -2.000 出错了;

这个实验说明了即使保证栈初始是 8 字节对齐的,编译器也只能保证在调用 sprintf 那个时刻栈是 8 字节对齐的,但不能保证任意时刻栈都是 8 字节对齐的,如果恰巧在 MSP 没有 8 字节对齐的时刻发生了中断,而中断中又调用了 sprintf,这种情况下仍会出错。

Cortex-M3 内核为我们做了什么

Cortex-M3 内核提供了一种硬件机制来解决上述这种中断中栈不对齐问题。

CM3 中可以把 NVIC 配置控制寄存器的 STKALIGN 置位,来保证中断中的栈 8 字节对齐。

具体实现过程如下:当发生中断时由硬件自动检测 MSP 是否 8 字节对齐,如果对齐了,则不进行任何操作,如果没有对齐,则自动将 MSP 减 4 这样便对齐了,同时将 xPSR 的第 9 位置位来记录这个 MSP 的非正常的变化,在中断返回若发现 xPSR 的第 9 位是置位的则自动将 MSP 加 4 调整回原来的值。

实验验证:

ddc6380c-433e-11ee-a2ef-92fbcf53809c.png



mian函数的反汇编如下:

ddda4aea-433e-11ee-a2ef-92fbcf53809c.png



1.在 A 处设置断点;

2.全速运行至 A,观察此时 MSP=0x200002e4 未对齐;

3.在 MDK 中将 SVC 的挂起位置 1,同时将 0xE000ED14 处的值由 0x00000000 改为 0x00000200(即将 NVIC 配置控制寄存器的 STKALIGN 置位)

4.在 B 处设置断点;

5.全速运行至 B,观察此时 MSP=0x200002b0 对齐了;

6.观察中断返回时的 MSP=0x200002e4 调整回来了;

7.继续全速执行,观察 buf 中的字符为:1.234 正确;

这个实验说明了将NVIC配置控制寄存器的STKALIGN置位可以保护中断时栈仍是8字节对齐

总结

综上所述,为了能够安全的使用严格遵守AAPCS规则的函数(比如sprintf)需要做到以下几点:

保证MSP在初始的时候是8字节对齐的

如果用到OS的话,需要保证给每个任务分配的栈是保持8字节对齐的

如果用的是基于CM3内核的处理器,需将NVIC配置控制寄存器的STKALIGN置位。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    18304

    浏览量

    222347
  • 寄存器
    +关注

    关注

    30

    文章

    5042

    浏览量

    117794
  • 存储器
    +关注

    关注

    38

    文章

    7154

    浏览量

    162035
  • SVC控制器
    +关注

    关注

    0

    文章

    2

    浏览量

    5213
  • 嵌入式单片机

    关注

    0

    文章

    10

    浏览量

    2227

原文标题:对堆栈 8 字节对齐问题的讨论

文章出处:【微信号:c-stm32,微信公众号:STM32嵌入式开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    AAPCS规则要求堆栈保持8字节对齐(转)

    我们保证了栈初始的时候8字节对齐的,那么编译器可以保证以后调用浮点库时
    发表于 01-19 11:43

    89C52堆栈的范围到底是低地址的128字节还是整个RAM?

    芯片的DPTR有两组,所以多2字节)寄存器和2字节PC,算起来最多是:8*2(普通子程序)+7(反汇编中断的直接PUSH和POP指令,状态寄存器)+2(中断现场)+2(中断的子程序)
    发表于 11-22 16:47

    单片机嵌入式系统的区别 单片机嵌入式linux区别

    ,Zilog公司推出了Z80系列,这些早期的单片机均含有256字节的RAM、4K的ROM、4个8位并口、1个全双工串行口、两个16位定时器。之后80年代初,Intel又进一步完善了8
    发表于 03-16 16:22

    请问F28335字节对齐能不能改为1字节

    大家好,C2000成员TMS320F28335CCS3.3下是2字节对齐的,能不能改为1字节对齐?怎么改
    发表于 08-20 06:41

    为什么MMU实验不是4字节对齐

    字节对齐我找了一下完全开发手册,里面的MMU段地址转换过程图如下所示也就是说,这里的VA是右移18位,并不是像韦老师源码里的右移20位,可是我把韦老师的源码改为右移18位后,发现程序无法正常跑起来,这是为什么?有没有人亲手写过
    发表于 08-05 03:26

    萌新求助,电脑串口发送9字节,其中包含1字节包头0x5a,和8字节的数据,将8字节数据从小到大排序

    萌新求助,电脑串口发送9字节,其中包含1字节包头0x5a,和8字节的数 据,将8字节数据从小到大
    发表于 11-01 13:44

    align为什么8字节对齐

    我知道数据储存的起始地址%对齐字节(N)=0才行,但是我不明白有两点问题1:UCOSIII的系统的浮点数打印任务的堆栈大小
    发表于 04-23 00:21

    请问嵌入式开发时大多是不是驱动编程

    对于单片机开发来说,嵌入式开发时大多是不是驱动编程
    发表于 06-08 10:18

    请问嵌入式单片机之间的关系是什么?

    时间回到十五年前,大部分人搞嵌入式,其实是做单片机,那时单片机资源少(我曾用过128字节RAM的MCU,仔细扣每一
    发表于 06-24 14:35

    详解STM32单片机堆栈

    学习STM32单片机时候,总是能遇到“堆栈”这个概念。分享本文,希望对你理解堆栈有帮助。 对于了解一点汇编编程的人,就可以知道,
    发表于 01-12 11:30

    嵌入式linux和单片机

    嵌入式linux和单片机嵌入式物联网开发,linux,单片机32一.嵌入式介绍二.linux系统介绍1.ubuntu使用安装2.shell
    发表于 07-14 07:00

    【原创】嵌入式系统中大小端和对齐问题

    作者:黄忠老师(张飞实战电子高级工程师)C语言是一种高级语言,大多数情况下C语言的代码是和具体的处理器体系结构无关的。然而,嵌入式系统的编程
    发表于 07-30 09:34

    如何从单片机转为嵌入式Linux开发

    最近很多童鞋投票并咨询如何从单片机转为嵌入式Linux开发。看来读者圈单片机,RTOS的不少。尽管小编目前从事Linux/Android方面的
    发表于 11-22 07:08

    嵌入式单片机开发到底怎么样

    大家好,我是无际单片机编程团队徐工“嵌入式单片机开发到底怎么样?”今天有个兄,开始接触并学习单片机的,在学校学习了2年。虽然
    发表于 11-26 06:41

    嵌入式单片机编程是怎么样的

    师的思维的不同的思考》,经过一年多的深化,又有些新的感想,角度是不同的,本篇的角度是编程思想。想想嵌入式单片机编程是怎么样的?其实
    发表于 12-22 07:15