当前位置:首页 > 嵌入式 > wenzi嵌入式软件
[导读]为什么要内存对齐 虽然所有的变量最后都会保存到特定的地址内存中去,但是相应的内存空间必须满足内存对齐的要求,主要基于存在以下两个原因: 硬件平台原因:并不是所有的平台都能够访问任意地址上的任意数据,某些硬件平台只能够访问对齐的地址,否则就会出


为什么要内存对齐

虽然所有的变量最后都会保存到特定的地址内存中去,但是相应的内存空间必须满足内存对齐的要求,主要基于存在以下两个原因:

  • 硬件平台原因:并不是所有的平台都能够访问任意地址上的任意数据,某些硬件平台只能够访问对齐的地址,否则就会出现硬件异常错误。

  • 性能原因:如果数据存放在未对齐的内存空间中,则处理器在访问变量时要做两次次内存访问,而对齐的内存访问只需要一次。

上述两个原因,第一个原因从字面意思上就能够理解,那第二个原因是什么意思呢?假定现在有一个 32 位微处理器,那这个处理器访问内存都是按照 32 位进行的,也就是说一次性读取或写入都是四字节。假设现在有一个处理器要读取一个大小为 4 字节的变量,在内存对齐的情况下,处理器是这样进行读取的:那如果数据存储没有按照内存对齐的方式进行的话,处理器就会这样进行读取:对比内存对齐和内存没有对齐两种情况我们可以明显地看到:在内存对齐的情况下,只需要两个个步骤就可以将数据读出来,首先处理器找到要读出变量所在的地址,然后将数据读出来。在内存没有对齐的情况下,却需要以下四个步骤才能够将数据取出来:

  • 处理器找到要读取变量所在的地址,也就是图中红色方块所在位置。

  • 由于此时内存未对齐,处理器是 32 位的,一次性读取或者写入都是 4 字节,所以需要将 0-3 地址内的数据和 4-7 地址里的数据都取出来。

  • 由于 0 - 3 地址范围的 0 地址里的数据不属于我们要读取的数据,因此将这一小块的数据进行移位,把 0 地址里的数据移出去;同理, 4 - 7 地址范围里的数据也要进行移位,保留 4 地址里的数据

  • 合并移位之后的数据,得出结果

通过上述的分析,我们可以知道内存对齐能够提升性能,这也是我们要进行内存对齐的原因之一。

结构体内存对齐

对齐原则

在明白了为何要进行内存对齐之后,我们来分析结构体内的内存对齐,在进行具体的实例分析前,需要给出结构体内存对齐的两条基本原则。

  • 结构体各成员变量的内存空间的首地址必须是“对齐系数”和“变量实际长度”中较小者的整数倍。

  • 对于结构体来说,在其各个数据都对齐之后,结构体本身也需要对齐,即结构体占用的总大小应该为“对齐系数”和“最大数据长度”中较小值的整数倍。

在给定了基本原则之后,我们通过一个例子来说明结构体的内存对齐,假定当前的处理器是 32 位的,对齐系数为4。在这里笔者选择在上一篇文章中涉及到的一个结构体进行解析,结构体如下:

   
  1. struct data_test

  2. {

  3. char a; /*本身大小 1 字节*/

  4. short b; /*本身大小 2 字节*/

  5. char c[2]; /*数组单个成员 1 字节*/

  6. double d; /*本身大小 8 字节*/

  7. char e; /*本身大小 1 字节*/

  8. int f; /*本身大小 4 字节*/

  9. char g; /*本身大小 1 字节*/

  10. }data;

根据我们刚刚给出的第一条对齐原则,先确定出每个变量的存储位置,变量存储方式是小端对齐,为了看起来更加形象,以 16 个字节作为一行来表示变量的存储位置(这里所说的存储位置是指相对于结构体起始地址的偏移)。根据第一条规则:各成员的内存空间的首地址必须是对齐系数和变量本身大小较小者的整数倍,这里对齐系数是 4,因此变量 a 、数组 c 、变量 e 、变量 g 的首地址需要满足 1 的倍数,变量 b 的首地址需要满足 2 的倍数,变量 d 的首地址需要满足 4 的倍数,变量 f 的首地址需要 4 的倍数。所以也就有了上述表格中的变量存储位置。那既然结构体内的成员都已经对齐了,为什么还存在第二条原则呢?也就是说为什么结构体内的成员已经内存对齐了,结构体本身还需要对齐?下面通过一个结构体数组来说明,比如我们定义了这样一个结构体数组:

   
  1. struct data_test

  2. {

  3. char a; /*本身大小 1 字节*/

  4. short b; /*本身大小 2 字节*/

  5. char c[2]; /*数组单个成员 1 字节*/

  6. double d; /*本身大小 8 字节*/

  7. char e; /*本身大小 1 字节*/

  8. int f; /*本身大小 4 字节*/

  9. char g; /*本身大小 1 字节*/

  10. }data[2];

我们在放置成员存储位置的时候,data[0] 按照成员对齐的原则依次存放,放到最后一个结构体成员时,如果不考虑结构体本身的对齐,按照数组元素是紧挨着存放的原则,那这个结构体数组应该是按照下图进行存储的:从上图中我们可以看到虽然 data[0] 中的成员都对齐了,但是由于结构体本身的不对齐,导致 data[1] 中的好多成员都不对齐了,因此,在完成了结构体成员的内存对齐后,我们还需要依据第二条原则:结构体占用的总大小应该为“对齐系数”和“最大数据长度”中较小值的整数倍,来对结构体本身进行对齐,因此正确的结构体数组的存储位置应该如下图所示:这里需要注意的是,上述原则针对的是结构体占用的总大小,而不是结构体的首地址,所以,在结构体本身还没有对齐的情况下,data[0] 的大小是 25 个字节,但是根据上述原则,在对齐系数为 4 的前提下,结构体大小应该是 4 的整数倍,所以要对结构体进行所占内存进行填充,因此:data[0] 最终的大小是 28 字节,结构体数组 data 的大小为 56 字节

结构体内成员顺序

通过上述分析我们可以很容易就想到,根据第一条原则,那么结构体成员定义的先后顺序会对最终结构体占用的内存造成影响,比如现在调整结构体 data 内成员的定义顺序,如下:

   
  1. struct data_test

  2. {

  3. char a; /*本身大小 1 字节*/

  4. char c[2]; /*数组单个成员 1 字节*/

  5. char e; /*本身大小 1 字节*/

  6. char g; /*本身大小 1 字节*/

  7. short b; /*本身大小 2 字节*/

  8. int f; /*本身大小 4 字节*/

  9. double d; /*本身大小 8 字节*/

  10. }data;

改变结构体成员顺序后的存储位置如下:通过图片就可以看到只有一块蓝色的填充区域,在成员对齐之后,结构体大小是 20 ,已经是 4 的整数倍,已经无须再做填充,所以调整顺序后的结构体大小为 20 个字节,相比于之前没有改变顺序之前整整减少了 8 个字节,也可以看出结构体成员的定义顺序也是需要关注的一个问题,关于结构体内成员定义的顺序应该遵循这样一个原则:按照长度递增的顺序依次定义各个成员

如何设定对齐系数

查看默认对齐系数

在上述我们对结构体内存对齐的分析中,我们都是假定对齐系数为 4 ,实际上对于编译器来说都有默认的对齐系数,我们可以输入伪指令,然后以报警信息的方式显示当前的对齐系数:

   
  1. #pragma pack(show)

设置对齐系数

   
  1. #pragma pack(1) /*设置一字节对齐*/

  2. struct data_test

  3. {

  4. char a;

  5. short b;

  6. char c[2];

  7. double d;

  8. char e;

  9. int f;

  10. char g;

  11. } data;

  12. #pragma pack()/*取消一字节对齐,恢复默认对齐系数*/

在这里,设置1字节对齐其实也就相当于不进行内存对齐,因为任何地址都可以是 1 的整数倍,最后,需要注意的是使用这种方法设置字节对齐,要在想要取消一字节对齐的地方使用伪指令 #pragma pack() 取消一字节对齐,否则后面所定义的结构体会继续采用刚刚所设置的对齐方式。除了采用上述这样设置一字节对齐的方式取消内存对齐,也可以采用下面的方式取消字节对齐:

   
  1. struct __attribute__((packed)) data_test2

  2. {

  3. char a;

  4. short b;

  5. char c[2];

  6. double d;

  7. char e;

  8. int f;

  9. char g;

  10. }data;

这种方式相对于上述方法来讲,不用执行取消操作,作用域只是本结构体,不会影响其他结构体的对齐方式。最后,取消字节对齐的结构体(或者说是按照 1 字节对齐的结构体)data 的大小就是 19 个字节,即将结构体内的所有成员的字节大小相加即可。

总结

了解结构体的内存对齐,从而在定义结构体成员时按照最优的顺序进行定义,对于 RAM 资源比较紧缺的 MCU 来讲,也是非常重要的。同时,在笔者的上篇文章《union 的概念及在嵌入式编程中的应用中》,所讲到的运用 union 和 struct 嵌套来便捷地解析数据,也应该取消字节对齐(因为上篇文章最后一个例子结构体成员大小都是一个字节,内存对齐取消与否都不影响成员的存储位置,所以没取消)。

参考资料 [1] https://aticleworld.com/data-alignment-and-structure-padding-bytes/

[2] https://blog.51cto.com/zhangyu/673792

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

北京2026年5月12日 /美通社/ -- 近日,中国移动年度盛会--2026移动云大会在苏州隆重召开。本届大会以"移动云 智能新空间"为主题,聚焦AI、算力、Tokens应用,共探智能转型与算力融合...

关键字: 移动 AI 指令集 内存

在系统编程与底层开发领域,结构体作为一种复合数据类型,是构建复杂数据模型的核心工具。然而,结构体的内存布局并非简单的字段大小叠加,其实际占用空间受数据对齐、编译器优化、语言特性等多重因素影响。错误估算结构体大小不仅会导致...

关键字: 编译器 内存

在Linux系统的运维与优化工作中,内存管理始终是核心环节。理解内存、Swap、Cache和Buffer的作用与运行机制,不仅能帮助我们准确判断系统资源状态,更是实现性能调优的关键。

关键字: Linux系统 内存

RK3506 本身就是为低成本、高性价比设计的方案,板级内存和 NAND 容量都不会给得太奢侈。在这种资源有限的平台上,Qt 方案过于臃肿,一个基础界面就能把内存占掉一大截,再叠上业务逻辑和后台服务,压力直接拉满。很多低...

关键字: 内存 NAND 开发板

北京2026年4月23日 /美通社/ -- 摘要:上海开赟数字技术有限公司(下文简称开赟)联手IBM,基于IBM Spectrum LSF(下文简称LSF)高性能作业调度和资源管理平台,针对内存资源优化,推出了一套 &q...

关键字: IBM 内存 PC HP

全球电子协会最新报告指出,人工智能需求正重新分配内存供应,导致电子制造企业成本上升、交付周期延长

关键字: AI驱动 内存 电子制造

北京2026年4月3日 /美通社/ -- 4月2日,浪潮信息直播发布业界首个企业级OpenClaw方案——「企千虾」,为企业规模化部署、管理和应用OpenClaw,打造了安全、高效、易用的全链路方案。实操演示了「企千虾」...

关键字: OPENCL TOKEN AI 内存

3月24日消息,近日,摩根士丹利发布的最新智能手机市场研究报告指出,2026年安卓手机出货量同比降幅或将达到15%,而iPhone出货量降幅仅约2%受影响较小。

关键字: 内存 DDR5
关闭