废话不多说直接上代码:
struct LDQStruct1 {
double a;
char b;
int c;
short d;
}struct1;
struct LDQStruct2 {
double a;
int c;
char b;
short d;
}struct2;
NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
上面代码定义了两个结构体struct1、struct2,可以看到这两个结构体里面的内容是一样的,不一样的是交换了b
和c
的位置。
运行之后得到结果:24-16
那么为什么两个相同的结构体只是其中两个元素的位置不一样它们的大小就不同呢?这就是内存对齐
之后的结果。
内存对齐的规则
- 数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储。 min(当前开始的位置mn)m=9 n=4 9 10 11 12
- 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
- 收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补⻬。
那么下面就来一步步看看上面那两个结构体是怎么内存对齐的。
这里提供常见数据类型所占内存大小的表:
内存大小.png
struct1
struct LDQStruct1 {
double a;
char b;
int c;
short d;
}struct1;
分析:
- a占8个字节,从offset为0开始,a的位置是0-7
- b占1个字节,从8开始,这里8刚好是1的整数倍,所以b的位置就是8.
- c占4个字节,从9开始,显然9不是4的整数倍,那么继续往后面数10、11、12,数到12是4的整数倍,那么c就是从12开始,12-15
- d占2个字节,从16开始,刚好16是2的整数倍,那么d就是16-17
所以结构体struct1的长度是0-17为18,根据规则3,结构体长度要是里面最大元素的整数倍,所以struct1的最终长度为24.
struct2
struct LDQStruct2 {
double a;
int c;
char b;
short d;
}struct2;
分析:
- a和上面一样0-7
- c占4个字节,从8开始刚好,c的位置8-11
- b占1字节,从12开始刚好,b为12
- d占2字节,从13开始不行,从14开始刚好,d为14-15
struct2的长度是0-15为16,16刚好是8的整数倍,所以struct2的最终长度是16.
以上就是我对内存对齐的理解和分析。
下面再来看以个结构体里包含结构体的例子:
struct LDQStruct3 {
double a;
char b;
int c;
short d;
struct LDQStruct2 uct;
}struct3;
看上面的结构体struct3,里面a、b、c、d的类型和顺序都和struct1是一模一样的,它只是比struct1多了一个结构体struct2.
所以我们看到struct1分析的第4步,d的位置是16-17。
然后来看uct,由于uct是一个结构体,它要从8的整数倍开始存储,所以uct从24开始存储:
- uct里面的a就是从24开始,而a的长度是8,24刚好是8的整数倍,所以它从24开始,为24-31.
- uct里面的c就是从32开始,刚好就是32-35
- uct里面的b刚好就是36
- uct里面的d,从37开始不行,不是2的整数倍,d为38-39.
最后根据内存对原则可以知道struct3里面最大元素长度为8,0-39长度40,40刚好是8的整数倍,所以struct3长度为40.
同样也可以在代码里面打印一下,会发现输出的长度也是40;
注意:结构体指针的内存大小才是8字节,而不是结构体是8字节
内存对齐的好处
CPU存取原理
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。每次内存存取都会产生一个固定的开销,减少内存存取次数将提升程序的性能。所以 CPU 一般会以 2/4/8/16/32 字节为单位来进行存取操作。我们将上述这些存取单位也就是块大小称为(memory access granularity)内存存取粒度。
所以在读取对齐的内存效率更高,在读取未对齐的内存时,CPU需要做两次访问,而读取对齐的内存只需要访问一次,这大大提升了CPU的效率,提升了性能。