一文带你深入了解《C语言对齐与非对齐访问》(ARM指令集)

首先你需要知道在什么情况下你才需要用到对齐与非对齐这个概念

typedef struct {
	char* pCmd;				//4个字节												
	char *pCmdPara;         //4个字节           					
	char isFree;			//4个字节												  
}AT_QUEUE_ITEM_T;

AT_QUEUE_ITEM_T q[500] = {0}; //12*500 = 6000个字节

上面的代码 isFree 会应为对齐的原因填充成4个字节。所以原本只有9*500=4500个字节的数据,在这里硬生生变成了6000个字节,浪费了1500个字节。这对资源本就有限的单片机来说是致命的。

有没有解决方案呢?答案当然是有的。

#pragma pack(1)  //强制一字节对齐
typedef struct {
	char* pCmd;				//4个字节												
	char *pCmdPara;         //4个字节           					
	char isFree;			//1个字节												  
}AT_QUEUE_ITEM_T;
#pragma pack()

AT_QUEUE_ITEM_T q[500] = {0}; //9*500 = 4500个字节

这里我们使用 #pragma pack(n) 的预编译指令,将结构体强制1字节对齐。

如果用该结构声明的变量要作为参数传递入函数内部怎么办?

IAR 提供了 __packed属性

#pragma pack(1)  //强制一字节对齐
typedef struct {
	char* pCmd;				//4个字节												
	char *pCmdPara;         //4个字节           					
	char isFree;			//1个字节												  
}AT_QUEUE_ITEM_T;
#pragma pack()

AT_QUEUE_ITEM_T q[500] = {0}; //9*500 = 4500个字节

void test(AT_QUEUE_ITEM_T __packed par )
{

}

(备注:关于#pragma pack() 这个预编译指令的详细解释可以看这篇文章。 很多人不了解的嵌入式C高级用法_boazheng的博客-CSDN博客

你以为到这里就完了?

就这??

NO!NO!NO!

图样图森破!

首先并不是所有硬件平台都支持非对齐访问,且并不是所有操作都支持非对齐。

所以你首先的知道

什么是数据的对齐和非对齐

下面我们来看一下Cortex-M3与Cortex-M4权威指南 6.6的内容

 

 划重点:

ARM7/9/10只允许对齐传输,Cortex-M3和4 支持非对齐传输,非对齐传输不能在多加载/存储指令,栈操作指令,排他访问指令,位段操作中使用,且非对齐访问会降低性能。

文中没有对加载/存储指令给出明确说明,哪....

什么是加载/存储指令

存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则完成相反的操作。常用的加载存储指令如下:

那么在Cortex-M3与Cortex-M4权威指南中提到的多加载是什么鬼?

在上表中所有数据类型的是32的都不支持,多载因该就是LDM,LDRD这2个了,存储就是STR,STM,STRD,PUSH。

而STRB,LDRH正是这几个指令的存在才允许你非对齐访问。

我们知道了什么是加载,存储的指令,哪。。。。

什么是排他访问

Cortex-M3与Cortex-M4权威指南中是这样描述的。

 说人话就是。。。

我们知道临界区使任务有秩序地访问共享资源,避免资源竞争。但全局中断屏蔽不仅需要许多额外的指令周期,而且降低了紧急中断的响应速度。的排他访问旨在改善这一问题。

ARMv7指令集包含三对同步原语 ( synchronization primitives ) 。这三对同步原语分别为:

32位数据读写的 LDREX 和 STREX,
16位数据读写的 LDREXH 和 STREXH,
8位数据读写的 LDREXB 和 STREXB,
此外,还有用于清零排他访问标识的 CLREX 

这些同步原语给多任务提供了一种不阻塞的资源竞争应对方法 —— 排他访问。排他访问实现有保障的 “ 读 - 改 - 写 ” 操作,可以用于信号量、自旋锁等系统服务。

请看图。。。。

 所以我们可以看出排他访问主要用在有嵌入式系统的情况下,信号量,自旋锁,互斥锁等等的应用上。

本文精华所在:

罗里吧嗦的说了这么多干巴巴的感念,到底有什么用?

那就是非对其访问什么时候用,怎么用?什么时候不能用。

1.在IAR中所支持的硬件平台

 这里我们只关注ARM部分。在ARM中在Cortex-M3与Cortex-M4都是支持非对其访问的,但Cortex-M0不支持。

2.在什么样的情况下不要用。

使用__packed#pragma pack 有一个显著的缺点#pragma pack是每次访问结构中未对齐的元素都会使用更多的代码,也就是说在在需要高性能的地方最好不要用,如果一定要用,请自己平衡高性能和存储空间之间的矛盾。

其次函数内部直接使用非对其访问会影响出栈入栈,所以可以将非对齐访问的内容放在堆里,比如用Malloc或者静态变量来申明他的实体。

还有,不要对信号量类型的变量使用__packed属性修饰符。

3.什么时候用

例如本文一开始说的可以节省大量空间的地方,还有我们在解析或封装通信字节流的时候,或者需要写入Flash的结构等。。总的来说就是用在需要节约空间的地方。

相关资料阅读:

IAR关于未对其访问的介绍

Accessing Unaligned Data | IAR Systems

微软关于#pragma pack的介绍

pack pragma | Microsoft Learn

扩展阅读:

关于C语言怎么分配内存的资料

 【C】从内存出发理解C语言变量作用域与生命周期_NjustMEMS_ZJ的博客-CSDN博客

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂奔De鸵鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值