在计算机系统中,为方便,快速的从内存中读取内存值,要求内存中的变量值需要按照一定的规则进行排放,在这篇文章中我们一起讨论结构体中的内存对齐。
内存对齐原则:
- 1:
数据成员对齐原则
:结构体(struct)
或联合体(union)
的数据成员,第一个数据成员
放在offset
为0
的地方,以后每个数据成员存储的起始位置
要从该成员大小
的整数倍
开始。 - 2:
结构体作为成员
:如果一个结构里有某些结构体成员
,则结构体成员
要从其内部最大元素大小
的整数倍地址
开始存储。 - 3:
收尾工作
: 结构体的总大小,也就是sizeof
的结果,必须是其内部最大成员
的整数倍
,不足的要补齐。
分析:
单个结构体
我们通过探讨下面两个结构体的内存分布,来探讨该问题
//1、定义两个结构体
struct Mystruct1{
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
}Mystruct1;
![内存对齐2.png](https://img.haomeiwen.com/i1891462/460d8737b79e80e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
//计算 结构体占用的内存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));
我们假设从内存为 0
的位置开始存储
对于MyStruct1
而言:
- 1,
char a
:a
为char
类型,占用1字节
,位置 0
存放char a
。 - 2,
double b
:b
为double
类型,占用8字节
,根据原则1
,要从8 的整数倍
位置处存放b,则在位置8
存放double b
的值。到目前为止,0~7
存放的是char a
,8 ~ 15
存放的是double b
。 - 3,
int c
:c
为int
类型,占用4字节
,根据原则1
,需要存放在4的整数倍
的位置,则 从位置16
开始存放。16~20
存放int c
。此时 ,0~7
存放的是char a
的值,8~15
存放的是double b
的值,16~19
存放的是int c
的值。 - 4,
short d
:d
为short
类型,占用2字节
,应该从2的整数倍位置
开始存储。则在20~21
位置处存放short d
的值。
MyStruct1
结构体的内存分布如下图所示,白色
区域为填充区域,有颜色的地方
为存放变量的区域。根据原则3
,结构体的大小为最大成员变量的整数倍
原则,我们可以看出,其大小为 24.
对于Mystruct2
而言:
- 1,
double b
为第一个成员,从0位置
开始存储,占用8
字节,则0~7
存放double b
的值。 - 2,
int c
需要占用4字节
,需要从4的整数倍开始存储
,则4~7
存放int c
的值。 - 3,
short d
需要占用2字节
,从2的整数倍位置开始存储,则8~9
存放short d
的值。 - 4,
char a
需要占用1
字节,从1 的整数倍位置开始存储,则位置10
存放char a
的值。
下图为Mystruct2
的内存分布,我们可以看出 Mystruct2
的内存大小为 16
⚠️⚠️⚠️我们通过上面两个例子,来讲解结构体的内存对齐
原理,我们可以看出,当结构体的成员一样时,其占用的内存大小并不一致,其内存大小与成员的排列顺序有关系
。
结构体嵌套
为了分析结构体嵌套里面的内存分布,我们定义如下
struct Mystruct2{
double b; //8字节
int c; //4字节
short d; //2字节
char a; //1字节
}Mystruct2;
struct MyStruct3{
char a; //1字节
double b; //8字节
struct Mystruct2 struc; // 16 字节
int c; //4字节
short d; //2字节
}MyStruct3;
在 MyStruct3
中嵌套了 Mystruct2
:
- 1,根据前面单个结构体里面阐述的
char a
占用0 ~ 7
字节空间,double b
占用8 ~ 15
字节空间。 - 2, 根据
原则2
,Mystruct2
结构体里面最大的成员变量
为8字节
,所以,要从8的整数倍的位置
处开始存放Mystruct2
,16
是8的整数倍
,所以,16 ~ 31
存放Mystruct2
。 - 3,
int c
,需占用4字节
,需要从4的整数倍
位置处存放,则32 ~ 35
处存放int c
, - 4,
short d
,需要占用2字节
,从2的整数倍
位置处开始存放,则36 ~ 37
存放short d
。
根据以上分析,我们可以得到,MyStruct3
占用了 0~37
位置的空间,根据 原则3
,它所占总空间应该为 其内部最大成员大小的整数倍
,MyStruct3
的最大成员大小为 8,所以,其占用内存空间为 0 ~ 39
,共 40
字节空间。其内存分布如下图所示:
内存优化(属性重排)
在我们讲解第一个示例的时候,我们可以看出,结构体内存的大小和成员变量的大小和其排放顺序有关
,两个结构体如果拥有同样的属性,但其属性排放顺序不一致,会导致占用内存的大小不一致。原因如下:
- 如果结构体内存成员变量是从小到大进行排布的,则需要更多的
padding
会导致占用的内存偏大。 - 如果结构体内,成员变量是从大到小进行排布,则会有很少的
padding
,会使结构体占有更少的空间。
在 iOS中,苹果会对进行属性重排
,来达到优化内存
的目的,我们以下面的代码为例
@interface LYPerson : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) char a;
@property(nonatomic, assign) int c;
@property(nonatomic, assign) short d;
@end
@implementation LYPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYPerson *person = [LYPerson alloc];
person.c = 5;
person.a = 'a';
person.name = @"LY";
person.d = 2;
NSLog(@"%ld", class_getInstanceSize(person.class));
}
return 0;
}
首先,我们来看下输出结果:
2020-09-12 13:24:17.099525+0800 KCObjc[59477:1943607] 24
为什么是24 ???
我们可以通过lldb
指令查看一下内存分布情况
(lldb) po person \\ 1
<LYPerson: 0x1012492c0>
(lldb) memory read/8gx 0x1012492c0 \\ 2
0x1012492c0: 0x001d80010000238d 0x0000000500020061
0x1012492d0: 0x0000000100001010 0x0000000000000000
0x1012492e0: 0x0000000000000000 0xbd2293c8794d4e12
0x1012492f0: 0x00000001003f4000 0x0000000000000001
(lldb) x/8gx 0x1012492c0 \\ 3
0x1012492c0: 0x001d80010000238d 0x0000000500020061
0x1012492d0: 0x0000000100001010 0x0000000000000000
0x1012492e0: 0x0000000000000000 0xbd2293c8794d4e12
0x1012492f0: 0x00000001003f4000 0x0000000000000001
- 1,打印 person对象,得到 person对象的内存地址。
- 2,读取
0x1012492c0
内存区域的存储值,memory read/8gx 地址
指令用来读取内存值, - 3,
x/8gx 0x1012492c0
:x
是memory read/
的缩写行式。8,是输出的数量个数,g
是giant word 8字节
,最后一个x
表示以16进制格式输出
。
(lldb) po 0x001d80010000238d & 0x00007ffffffffff8ULL // 1
LYPerson
(lldb) po 0x0000000100001010 // 2
LY
(lldb) po 0x0005 //3
5
(lldb) po 0x0002 // 4
2
(lldb) po 0x0061 //5
97
- 1,在该内存的
前8个字节
存储的是 对象的isa
值,0x00007ffffffffff8ULL
为ISA_MASK
。 - 2,从
16 ~ 23
字节处,储存的是对象的name
属性。 - 3,
10 ~ 11
字节处,储存的是属性c
的值。 - 4,
12 ~ 13
字节处,存储的是属性d
的值。 - 5,
14 ~ 15
字节处,存储的是属性a
的值,97
为字母a
的ASCII
值。
其内存分布如下所示:
LYPerson内存分布.png
总结:
我们在这里首先讨论了,内存对齐的三原则
,单个结构体的内存分布
,结构体嵌套的内存分布
,以及 iOS中的内存对象的属性重排和其内存分布
网友评论