1、内存对齐规则
-
数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,
第 一个
数据成员放在offset为0
的地方,以后每个数据成员存储的起始位置
要
从该成员大小
或者成员的子成员大小
(只要该成员有子成员,比如说是数组,
结构体等)的整数倍
开始存储(比如int为4字节
,则要从4的整数倍
地址开始存储)。 -
结构体作为成员:如果一个结构里有某些
结构体
成员,则结构体成员
要从其内部最大元素大小
的整数倍
地址开始存储。(struct a
里存有struct b
,b
里有char,int ,double
等元素,那b
应该从8的整数倍
开始存储)。 - 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍。不足的要补⻬。
-
关键点:
- 第一个数据成员放在
offset为0
的地方 - 以后起始位置为该成员大小的整数倍
- 结构体成员:起始位置为子结构体最大成员的整数倍
- 结构体总大小为最大成员整数倍
- 第一个数据成员放在
接下来我们先从一个简单结构体进行分析理解这个规则
数据成员对⻬规则
struct KKStruct1 {
double a;
char b;
int c;
short d;
}struct1;
- 第一步 接下来我们按照上述规则进行填充。
- 第一个成员
double 占8字节
放在offset为0
的地方。所占区域[0 - 7]
。 - 第二个成员
char占1字节
填充的起始位置
为char 1字节的整数倍
。所占区域[8]
。 - 第三个成员
int占4字节
填充的起始位置
为int 4字节的整数倍
。所以空出接下来的非4的整数倍
的区域[9,10,11]
,从12
开始填充所占区域[12,13,14,15]
。 - 第四个成员
short占2字节
填充的起始位置
为short 2字节的整数倍
。所占区域[16,17]
;
- 第一个成员
根据规则填充得到结构体struct1
所需要内存18
。但是分配内存需要按照最大成员8的整数倍
应分配得24
。
运行结果图1显示与我们按照规则计算的相同。
- 第二步为结构体
struct1
赋值,查看值存储情况进行进一步的验证
由于double
类型在二进制中不方便观察。这里我们将double
类型改为NSInteger
类型。注意NSInteger 在64位中占8字节,32位中占4字节。使用时确定使用的为64位设备
//结构体指针的内存大小 8
struct KKStruct1 {
// double a;//8 [0 - 7]
NSInteger a;
char b;//1 [8]
int c;//4 (9,10,11) [12,13,14,15]
short d;//2 [16 17]
}struct1;//8的倍数 24
int main(int argc, const char * argv[]) {
@autoreleasepool {
struct1.a = 13;
struct1.b = 'c';
struct1.c = 11;
struct1.d = 9;
NSLog(@"%p",&struct1);
NSLog(@"%zd",sizeof(struct1));
NSLog(@"Hello, World!");
}
return 0;
}
按照上面规则,我们输出的内存中存储数据应如结果图2
接下来我们读区内存中的数据检查是否是我们存储的数据结果图3
从结果中可以看到第一个和第四个确实是存储的成员数据
a = 13,d = 9
。但是第二个变成了一非常大的数据。这是因为在读取时按照一个固定的部长
8字节
进行读取的。所以读取的地址其实是存储着b和c
两个成员的内存地址图4
。取出地址在分开读区即可。这里输出的b = 99
这里99是字符c
对应的ASCII码
。图4
结构体作为成员
这里我们用沿用上面的已知结构体struct1
作为研究对象。另定义一个结构体struct2
作为struct1
的成员验证上述的规则。
- 第一步 定义简单的结构体
struct2
,一个成员char
。
struct2
只有一个成员char
。占1字节
。
将其作为struct1
的成员。按照规则结构体成员要从其内部最大元素大小的整数倍地址开始存储
进行填充。struct2
中只有char 1字节
。所以在struct1
的最后一个成员后面进行存储填充即可。
这里为了方便观察,为struct1
的成员d
和struct2
的成员b
进行赋值。
//结构体指针的内存大小 8
struct KKStruct2 {
char b;//1 [0]
}struct2;//1
struct KKStruct1 {
// double a;//8 [0 - 7]
NSInteger a;
char b;//1 [8]
int c;//4 (9,10,11) [12,13,14,15]
short d;//2 [16 17]
struct KKStruct2 s2;//内部成员最大1,[18]
}struct1;//8的倍数 24
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%p",&struct1);
NSLog(@"%zd",sizeof(struct1));
}
return 0;
}
按照规则struct2
内部成员最大1字节
的整数倍。填充存储。struct1
的所需内存不需要发生变化的。
结果图2.1
从结果图来开struct2
的成员是在struct1
的成员d
后面开始填充存储的。
- 第二步 为
struct2
添加成员int类型4字节
来进行观察
此时struct2
中最大成员为int类型4字节
。所以struct2
中的第一个成员char
存储的起始位置为int类型4字节的整数倍
。
struct1的d
成员结尾为17
,将空余18,19
,struct2
的char
从20
开始存储,空余21,22,23
,int
从24
开始存储24,25,26,27
。
此时struct1
的内存应为32
struct KKStruct2 {
char b;//1 [0]
int e;//4 空(123)[4 - 7]
}struct2;//8
struct KKStruct1 {
// double a;//8 [0 - 7]
NSInteger a;
char b;//1 [8]
int c;//4 (9,10,11) [12,13,14,15]
short d;//2 [16 17]
struct KKStruct2 s2;//最大为4 空(18,19)char从20开始,空(21,22,23),int [24,25,26,27]
}struct1;//8的倍数 32
结果图2.2
图2.2
struct1
的所需内存变成了28
。最大8字节的整数倍应为32
。
- 注意:成员结构体struct2也需要满足结构体内存对齐原则
struct KKStruct2 {
char b;//1 [0]
int e;//4 空(123)[4 - 7]
short c;//2 [8,9]
}struct2;//12
struct KKStruct1 {
int c;//4 [0-3]
double a;//8 (5,6,7)[8 - 15]
char b;//1 [16]
struct KKStruct2 s2;//最大为4 空(17,18,19),char[20],空(21,22,23),int [24,25,26,27] short[28,29],(内存对齐扩容(30,31))
short d;//2 [32 33]
}struct1;//8的倍数 40
int main(int argc, const char * argv[]) {
@autoreleasepool {
struct1.d = 2;
NSLog(@"struct1 :%zd struct2:%zd",sizeof(struct1),sizeof(struct2));
NSLog(@"%p",&struct1);
}
return 0;
}
结果图
内存信息图
结构体struct2
填充到29
就结束了,但是结构体需满足内存对齐
原则。所以为其扩容(30,31)
总共占12字节
,其最大成员的整数倍数
,所以short
从32
开始填充。内存为40字节
。
-
struct2
其内成员的最大字节
是影响struct1
大小的关键。 -
由于第一个成员之后的成员开始存储的位置为该成员大小的整数倍,所以后面难免会出现不是该成员大小
结果图(1,2,4,8)
的整数倍的情况。造成空余的位置。因此结构体成员的顺序会因空余位置造成所需内存大小的不同。
可以发现调整结构体成员的顺序,从而将中间空余的部分给利用了起来。从而节省了结构体所需的内存。
网友评论