1. 什么是内存对齐
看下面的小程序,理论上,int
占4 byte
,char占一个1 byte
,那么将它们放到一个结构体中应该占4+1=5byte
,但是实际上,通过运行程序得到的结果是8 byte
,这就是内存对齐所导致的。
struct Struct {
int a; // 4
char b; // 1
}struct4;
NSLog(@"%lu",sizeof(struct4)); // 输出为 8
计算机中内存空间都是按照 byte
划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k
(通常它为4
或8
)的倍数,这就是所谓的内存对齐。
2. 为什么要内存对齐
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
计算机的处理器是以一定大小的块来进行读取的,这作为我们的前提条件。
对齐跟数据在内存中的位置有关。如果一个变量的内存地址刚好位于它本身长度的整数倍,他就被称做自然对齐。例如一个整型变量(占4字节)的地址为0x00000008
,那它就是自然对齐的。
现在假设一个整型变量(4字节)不是自然对齐的,它的起始地址落在0x00000002
(图中蓝色区域),处理器想要访问它的值,按照4字节的块进行读取,从图中的0x00
起读,读取4字节大小,读到0x03
这样的一次读取之后,我们并不能取到我们要访问的整型数据,紧接着处理器会继续再往下读,偏移4个字节,从0x04
开始,读到0x07
到这一步,处理器才能读取到了我们需要访问的内存数据,当然这中间还存在剔除与合并的过程。在上面的例子中,要读取两次才能获取到我们想要的数据。
那么如果是内存对齐的呢?
由上图可知,只要读取一次就能获取到相应的数据。
因此可以得出,内存是否对齐,会影响到数据的读取效率。另外不同的内存存取粒度对同一任务也有不同影响,这里我们不过多的讨论。
3.内存对齐原则
- 数据成员对⻬规则:结构(
struct
)(或联合(union
))的数据成员,第一个数据成员放在offset
为0的地方(即首地址的位置),以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int
为4
字节,则要从4
的整数倍地址开始存储。- 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(
struct a
里存有struct b
,b
里有char
、int
、double
等元素,那b
应该从8
的整数倍开始存储.)- 收尾工作:结构体的总大小,也就是
sizeof
的结果,必须是其内部最大成员的整数倍,不足的要补⻬。
按我自己的理解来看:
- 对于规则1 每个数据成员的起始位置,都是自身大小的整数倍。
- 对于规则2 对于结构体做为成员变量,起始位置要根据自身成员变量最大的元素来确定。
下面我们来看一个简单的例子:
struct TestStruct {
double a; // 8
char b; // 1
int c; // 4
short d; // 2
}struct1;
double
的大小为8
个字节,按照规则1, a
成员变量将会占据前首地址开始的 8
个字节。也就是0x00
到0x07
的地址。
char
的大小为1
,按照规则1,内存中的第9
为是1
的倍数,占据一个字节0x08
,单数成员变量c
,为int
类型,大小为4
,内存中的第10
为并不是4
的倍数,所以并不能满足规则1。因此,成员变量b
要补3
个位置,即占据0x08
到0x11
的地址单元,成员变量c
从0x12
到0x15
开始占据4
个内存单元。同理,按照规则1可以得出成员变量d
占据0x16
到0x08
的内存单元,总共占据18
个字节。最后,我们进行收尾工作:结构体的总大小,必须是其内部最大成员的整数倍。
内部最大成员为double
8
个字节,可以计算出结构的总大小为24
。
最后我们用代码来验证下:
如果结构体中存在结构体成员变量会怎样?
struct TestStruct2 {
double a; // 8
int b; // 4
char c; // 1
short d; // 2
int e; // 4
struct TestStruct1 str;
}struct2;
按照规则1
,可以得出前5个变量占据24
个内存单元,对于结构体成员str
,其内部最大成员为double
8
个字节,而24
为8
的倍数,符合规则1
, str
占据后面24
个字节单元。此时,结构体总共占据48
个内存单元,按照规则3
TestStruct2
的最大成员是 TestStruct1 str
24
个字节,符合规则。
下面我么用代码验证下结果
4. 总结
其实,内存对齐就是定制了一套规则,以合理的利用内存空间并提高内存访问效率。 编译器通过适当增加padding
,使每个成员的访问都在一个指令里完成,而不需要多次访问再拼接。是一个以空间换时间
的过程。