建议先看下
IOS底层(三): alloc相关1.初探 alloc, init, new源码分析,
IOS底层(四): alloc相关: 对象属性在内存中的布局
首先先介绍IOS中3种获取内存方法
sizeof
class_getInstanceSize
malloc_size
sizeof
sizeof得到的是: 数据类型占用空间的大小
sizeof
是一个操作符
, 不是函数, 我们一般用sizeof
计算内存大小时, 传入的主要对象是数据类型, 这个在编译器的编译阶段
就会确定而不是运行时确定。
class_getInstanceSize
class_getInstanceSize 得到的是: 实例对象中成员变量的内存大小
class_getInstanceSize
是runtime
提供的api, 用于获取类的实例对象所占用的内存空间的大小
, 并返回具体的字节数。
malloc_size
class_getInstanceSize 得到的是: 系统实际分配的内存大小
这个是由系统完成的, 涉及16字节内存对齐
关于字节对齐, 我之前写的都是类的, 这次看下结构体的字节对齐, 例:
struct Mystruct1 {
char a; // 1字节
double b; // 8字节
int c; // 4字节
short d; // 2字节
} Mystruct1;
struct Mystruct2 {
double b;
int c;
short d;
char a;
} Mystruct2;
NSLog(@"结构体1占用的内存大小 %lu", sizeof(Mystruct1));
NSLog(@"结构体2占用的内存大小 %lu", sizeof(Mystruct2));
结果
为什么会是这个样子呢? 看到这个结果至少2个问题
- 1.这个内存是怎么计算的?
- 2.结构体1跟2的区别只是换了一个
char
位置, 为什么内存会变?
内存对齐原则
-
每个平台的编辑器都有自己的
对齐系数
, 程序员也可以通过预编译命令#pragma pack(n)
, n=1,2,4,8,16来改变这一系数, 其中n就是你指定的对齐系数
。在ios中xcode默认为8
, 即8字节对齐
。 -
数据成员对齐可以理解为
min(m, n)
公式, 其中m
表示当前成员开始位置
,n
表示当前成员所需要的位数
。如果满足m
整除n
(m % n == 0
),n
从m
位置开始存储, 反之m
循环+1
, 直至可以整除, 从而确定了当前成员位置。 -
数组成员为
结构体
, 当结构体嵌套结构体时, "成员"的结构体的自身长度为"成员"结构体中最大成员的内存大小, 例如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8 -
结构体的内存大小必须为结构体最大成员内存大小的整数倍
, 不足需要补齐
验证内存对齐原则
先看下ios中数据类型的占用内存大小, 方便我们之后计算
字节数对照表
针对于上面那个例子, 先看下这个图便于理解
结构体例子计算
结构体1
- a:
char
类型占1
字节, 因为 0 % 1 = 0, 即, 0储存a
- b:
double
类型占8
字节,
1 % 8 = 1, 不满足 +1,
2 % 8 = 2, 不满足 +1
......
8 % 8 = 0, 满足, 8储存b
- c:
int
类型占6
字节, 16 % 4 == 0, 16存储c
- d:
short
类型占2
字节, 20 % 2 == 0, 20存储d
结构体1需要内存22, 由于最大字节数为8
, 结构体必须是8的倍数, 所以需要向上取整, 固最终结果为24
结构体2
- b:
double
类型占8
字节, 0 % 8 == 0, 0储存b
- c:
int
类型占6
字节, 8 % 4 == 0, 8存储c
- d:
short
类型占2
字节, 12 % 2 == 0, 12存储d
- a:
char
类型占1
字节, 因为 14 % 1 = 0, 即, 14储存a
结构体1需要内存15, 由于最大字节数为8
, 结构体必须是8的倍数, 所以需要向上取整, 固最终结果为16
之前的问题解决了, 我们接下来看下结构体嵌套结构体
结构体嵌套结构体
struct Mystruct1{
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
}Mystruct1;
struct Mystruct2{
double b; //8字节
int c; //4字节
short d; //2字节
char a; //1字节
}Mystruct2;
struct Mystruct3 {
double b;
int c;
short d;
char a;
struct Mystruct2 str;
} Mystruct3;
看下结构体3(结构体3为结构体2基础上嵌一个结构体2), 如果按照我们之前的定义
-
结构体2为
16
, 且最大字节数8
, 那么作为成员结构体2, 字节数为也为8, 结构体3最大字节数为8
。 -
str:
struct
类型占16
字节, 16 % 16 == 0, 固从16起
结构体3需要内存32
, 由于最大字节数为8
, 结构体必须是8的倍数, 32满足, 不需要取整, 固结果应为32
, 我们验证一下
我们再做一下验证一下
struct Mystruct4{
int a;
struct Mystruct5{
double b;
short c;
}Mystruct5;
}Mystruct4;
-
先看内嵌结构体5中最大为
double
,8
字节,
0 % 8 = 0, 0开始放double
b,
8 % 2 = 0, 8放short
c, 别忘了整体需要最大值整数倍, 那么结构体5需要16字节 -
结构体4中, 首先
最大
为结构体5的8
字节,
0 % 4 = 0, 0开始放int
a,
8 % 8 = 0, 8开始放 结构体5, 总共需要24
-
因为24是8的整数倍, 故占用内存大小为
24
验证一下
结构体嵌结构体
内存优化(属性重排)
上面我们看到结构体1与结构体2里面属性是一样的, 只有排列位置不一样, 结果占用内存结果也不一样。所以结构体内存大小与结构体成员内存大小的顺序有关
-
如果是结构体中数据成员是根据内存
从小到大的顺序
定义的,根据内存对齐规则来计算结构体内存大小,需要增加有较大的内存padding即内存占位符,才能满足内存对齐规则,比较浪费内存 -
如果是结构体中数据成员是根据内存从
大到小的顺序
定义的,根据内存对齐规则来计算结构体内存大小,我们只需要补齐少量内存padding即可满足堆存对齐规则,这种方式就是苹果中采用的,利用空间换时间,将类中的属性进行重排,来达到优化内存的目的
结构体的看完了, 我们看下类中的属性重排
, 例如我们定义一个SATest
类
main
中给一些值, 然后我们读一下内存
这个我们我们可以看到 name
, age
, hobby
都读到可, 但是height
, c1
, c2
并没有读取到, 而且isa
旁边的内存段读出来是一串数字?
原因是由于苹果系统对属性进行重排, 0x0000001200006261
这一串就是age
, c1
, c2
。age
占4个字节,c1
,c2
占1个字节,通过4+1+1的方式,按照8字节补齐的方式存储在同一块内存中, 我们打印一下
这里留意一下char
类型是以ASCII码形式显示, 而地址为0x0000000000000000,表示person中还有未赋值
总结:
- 大部分内存都是固定以内存块进行读取
- 尽管我们在内存中采用内存对齐形式, 但是系统会自动对属性进行重排, 以此来优化内存
网友评论