内存对齐总结

相信大家对 “内存对齐” 这个名词肯定不会陌生,作为一名程序员,合理的使用内存可以提高程序的运行效率。同时,内存对齐也是笔试、面试中常常会遇到的一个问题。

本篇文章就从内存对齐方式、内存对齐作用来讲讲内存对齐的那些事。

内存对齐一般会涉及到结构体、联合体等,本篇就以结构体为例展开,结构体示例如下:

struct A{
    double a;
    char b;
    int c;
};

struct B{
    char a;
    double b;
    int c;
};

结构体内存对齐的方式

内存对齐的规则如下:

  • 对于结构体中的各个成员:第一个结构体成员位于偏移为 0 的位置,此后的每个结构体成员的偏移量都必须是 MIN(#pragma pack(X) , 结构体成员本身的类型长度) 的倍数(其中X的取值一般为 4 或 8 ,分别对应 32 位和 64 位操作系统)。
  • 所有结构体成员各自对齐之后,结构体本身也要进行对齐。结构体整体长度应该是 MIN(#pragma pack(X), 结构体中长度最长的结构体成员数据类型的长度) 的倍数。

为什么需要内存对齐?

内存对齐的作用有三个方面:

  • 内存对齐可以提升 CPU 的内存访问效率。因为 CPU 是按块去处理内存的,内存块的大小可以是 2、4、8、16 个字节,块的大小称为内存读取粒度。假设内存读取粒度为 4 ,CPU 从 0 字节开始的位置读取一个 4 字节的数据到寄存器中,那么 CPU 将会把 0 ~ 3 四个字节全部读取到寄存器中进行处理。
  • 内存对齐可以提升 CPU 的性能。基于上述,若数据起点位置不在 0 字节呢?假设数据起始位置为 2 字节,那么读取这个数据就需要 CPU 读取两次,首先读取 0 ~ 3,接着再读取 4 ~ 7,然后再把 0、1、6、7 字节的数据去除,最后合并 2、3、4、5 字节的数据放入寄存器中。上述就是内存未对齐的场景,此场景下寄存器做了很多额外的操作,必然会影响到 CPU 的性能。
  • 防止某些硬件平台拒绝处理数据。不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据(旧版 x86 架构等),否则就会抛出硬件异常。所以内存对齐时就不会存在平台差异,也就是代码的可移植性会更强。

案例分析

基于对结构体内存对齐方式的学习,我们现在可以试着分析一下文章开头所示的结构体,分别在 #pragma pack(4) 和 #pragma pack(8) 场景下的大小。

// #pragma pack(4)
struct A{
    double a;
    char b;
    int c;
};

struct B{
    char a;
    double b;
    int c;
};

在 32 位和 64 位机器上,以下数据类型的大小是相同的,各个数据类型本身长度如下:

  • double 类型 8 字节
  • char 类型 1 字节
  • int 类型 4 字节

基于第一章节中的对齐方式,在 #pragma pack(4) 场景下,struct A 对应的内存布局为:

  • double a 占用 8 字节(0 ~ 7)。
  • char b 占用 1 字节(8)。
  • 为了保持 int c 的 4 字节对齐,接下来会填充 3 字节的填充字节(9 ~ 11)。
  • int c 占用 4 字节(12 ~ 15)

即:| double a (8 bytes) | char b (1 byte) | padding (3 bytes) | int c (4 bytes) |

在 #pragma pack(4) 场景下,struct B 对应的内存布局为:

  • char a 占用 1 字节(0)。
  • 为了保持 double b 的 4 字节对齐,接下来会填充 3 字节的填充字节(1 ~ 3)
  • double a 占用 8 字节(4 ~ 11)
  • int c 占用 4 字节 (12 ~ 15)

即:| char b (1 byte) | padding (3 bytes) | double a (8 bytes) | int c (4 bytes) |

所以,在 #pragma pack(4) 场景下,struct A 结构体的总长度为 16 字节,struct B 结构体的总长度为 16 字节。

// #pragma pack(8)
struct A{
    double a;
    char b;
    int c;
};

struct B{
    char a;
    double b;
    int c;
};

基于第一章节中的对齐方式,在 #pragma pack(8) 场景下,struct A 对应的内存布局为:

  • double a 占用 8 字节(0 ~ 7)。
  • char b 占用 1 字节(8)。
  • 为了保持 int c 的 4 字节对齐,接下来会填充 3 字节的填充字节(9 ~ 11)。
  • int c 占用 4 字节(12 ~ 15)

即:| double a (8 bytes) | char b (1 byte) | padding (3 bytes) | int c (4 bytes) |

在 #pragma pack(4) 场景下,struct B 对应的内存布局为:

  • char a 占用 1 字节(0)。
  • 为了保持 double b 的 8 字节对齐,接下来会填充 7 字节的填充字节(1 ~ 7)
  • double a 占用 8 字节(8 ~ 15)
  • int c 占用 4 字节 (15 ~ 19)
  • 为了保持整个结构体总长度 8 字节对齐,接下来会填充 4 个 填充字节 (20 ~ 23)

即:| char b (1 byte) | padding (7 bytes) | double a (8 bytes) | int c (4 bytes) | padding (4 bytes) |

所以,在 #pragma pack(8) 场景下,struct A 结构体的总长度为 16 字节,struct B 结构体的总长度为 24 字节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

aSimpleSheep

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

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

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

打赏作者

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

抵扣说明:

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

余额充值