内存对齐

内存对齐,或者说字节对齐,是代码编译后在内存的布局与使用方式。现代计算机一般是32比特或64比特地址对齐,如果要访问的变量没有对齐,可能会触发总线错误。参考维基百科

什么是内存对齐

即某个地址A满足是n的倍数,其中n是2的幂次方(如1、2、4、8等等)。如果用二进制表示的话,那么 A的末尾至少有log2n个0。当我们说到某个变量是n字节对齐的时候,其意思是指这个变量的地址是对齐的。

内存对齐的意义

从我们编写的程序来看,CPU好像可以访问内存中的任意位置;但是实际上CPU往往是按照块为基本单位访问内存的。如果某个变量的起始地址位于某个块的的起始处,则只需较少的次数便能完成读取。 比如在某个CPU中,其每次取内存的大小为8字节,对于一个8字节的long类型变量,如果该变量的地址是8的倍数,那么每次load这个long变量只需要一次操作。如果不是8的倍数则需要两次,影响效率。

内存对齐的原则

  • 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组、结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。

  • 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(struct a里存有struct b,b里有char、int、double等元素,那b应该从8的整数倍开始存储)。

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

内存对齐的例子

我们以 64 位架构,即 8 字节对齐:

struct {
char a; // 1 byte offset = 0x00 0x00~0x07 8字节
double b; // 8 bytes 地址为8的倍数 offset = 0x08 0x08~0x0f 8字节
short c; // 2 bytes 地址为2的倍数 offset = 0x10 0x10~0x11 2字节
char d; // 1 byte 地址为1的倍数 offset = 0x12 0x12 1字节
} x; // 地址为8的倍数,0x00~0x17

struct {
double b; // 8 bytes 0x00~0x07
char a; // 1 byte 0x08
char d; // 1 byte 0x09
short c; // 2 bytes 0x0a~0x0b
} y; // 0x00~0x0f

NSLog(@"%lu - %lu", sizeof(x), sizeof(y)); //24 - 16

为什么两次输出的结果不一样的原因就是内存对齐的存在。

如果不考虑内存对齐,那么两次输出的就都应该是 12。

结构体的大小受其成员变量分布的影响。

address-align.png