这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
一、对象的内存空间
首先我们接上一篇所用的objc源码,打上断点运行。在控制台输入x p
,此时可以看到p
对象的内存情况。
0x10066e1d0
为首地址,后面则是内存。如何读取内存呢?由于iOS是小端模式,我们截取8位从后往前读取,在控制台打印出来。
打印出来的应该是isa
是首地址,但是这里看起来并不是。因为这里需要再& ISA_MASK
,在源码中找到在x86_64
下ISA_MASK
值为0x00007ffffffffff8
。& ISA_MASK
后打印出正确的isa
isa
后面一堆00
是对象所有属性的存储空间。
我们还是有另一种方式查看内存情况,点击菜单Debug
->Debug Workflow
->View Memory
。然后在Address
中输入首地址,这样也可以看到内存分布情况,即使对象属性没有赋值也会开辟内存。
下面我们给对象属性赋值后看下内存情况
将height
属性由long
改为BOOL
再看下内存情况
此时和上面的截图对比,我们发现age
和height
在一起。这就是今天要探讨的内存对齐
二、对象内存影响因素
对象内存的影响因素有哪些?我们先在控制台打印一下对象内存大小
删除部分属性后再打印,发现属性会影响内存大小
此时在声明文件加上一个成员变量后再打印,发现成员变量也会影响
最后我们再添加方法看下,内存大小不受影响(类方法也是如此)
三、结构体内存对齐
各类型所占字节大小
内存对齐原则
1、数据成员对⻬规则:
结构struct
(或联合union
)的数据成员,第⼀个数据成员放在offset
为0
的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int
为4字节
,则要从4
的整数倍地址开始存储)。
2、结构体作为成员:
如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储。(struct a
⾥存有struct b
,b
⾥有char
int
double
等元素,那b
应该从8
的整数倍开始存储。)
3、收尾⼯作:
结构体的总⼤⼩,也就是sizeof
的结果,必须是其内部最⼤成员的整数倍,不⾜的要补⻬。
结构体示例
先来看如下代码,struct1
和struct2
打印出的sizeof
是否相同呢?
struct LRStruct1 {
double a;
char b;
int c;
short d;
}struct1;
struct LRStruct2
double a;
int b;
char c;
short d;
}struct2;
NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
复制代码
最终打印结果为24-16
,经观察两个结构体变量类型、数量都一致,只是顺序不同,最终得到的内存大小也不同。结合上面三个内存对齐原则,分析如下
struct LRStruct1 {
double a; // 8 [0 7]
char b; // 1 [8]
int c; // 4 (9 10 11 [12 13 14 15]
short d; // 2 [16 17] 24
}struct1;
struct LRStruct2
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 16
}struct2;
复制代码
再看一个结构体嵌套结构体的示例
struct LRStruct3 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 16
int e; // 4 [16 17 18 19]
struct LRStruct1 str;// (20 21 22 23 [24 - 47]
}struct3;
复制代码
四、malloc源码引入
看下面这段代码,继续用LRPerson
打印结果为<LRPerson: 0x10041fcb0> - 8 - 40 - 48
结果分析:
sizeof
:对象类型的内存大小。person
是一个对象,里面是一个指针地址,占8字节class_getInstanceSize
:对象实际的内存大小。先前已经分析出内存大小是由类的成员变量的大小决定。LRPerson
有四个成员变量对应字节大小为8
8
4
8
,8字节对齐一下为32
,再加上isa
的8个字节,最终结果为40
malloc_size
:这里结果为什么是48
,不是40
呢?下面我们就探索下malloc_size
点击malloc_size
无法进入查看源码,根据路径发现这个文件来自usr/include
我们需要去苹果开源库下载libmalloc
源码
五、malloc分析探索
malloc_size
查看系统分配内存大小。根据上一篇alloc探索可知先由calloc
开辟内存空间,我们先看下calloc
的实现
跟进_malloc_zone_calloc
查看实现方法
在实现方法中有一行关键的代码,但是走到这里我们发现这行代码的calloc
再点击只有声明没有实现
ptr = zone->calloc(zone, num_items, size);
复制代码
此时在这行代码下个断点,断住后control + step into
跟进去(也可po zone->calloc
查看(有赋值就有存储值)),进入到default_zone_calloc
方法
用同样方法继续往下跟进去,进入nano_calloc
方法
这里定位到一行关键代码,进入_nano_malloc_check_clear
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
复制代码
在_nano_malloc_check_clear
的实现中看到我们关心的内存大小size_t
这行代码,这里接收了我们最开始传入的size: 40
,得到结果slot_bytes
为48
。所以关键在segregated_size_to_fit
这个方法,进去瞅一眼
我们发现这里对size
进行了处理,将((size + 16 - 1) >> 4) << 4
即取16的整数倍,就是16字节对齐
,所以malloc_size
查看的内存大小都是以16字节对齐
在系统的堆区,对象的内存以16字节对齐
而成员变量的是以8字节对齐
六、对象内存对齐原理
对象内存为何是16字节对齐
,而不是8字节对齐
?原因如下:
- 假使有3个对象都是8字节,如果是
8字节对齐
,那么3个对象就会紧密相连,容易产生访问错误。采用16字节对齐
则可以提高容错率- 对象都来自
NSObject
,本身有一个isa
,对象基本都会有成员变量,而成员变量是8字节对齐
,所以对象最小也要16字节
补充
x/5gx
格式化输出: