这是我参与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有四个成员变量对应字节大小为8848,8字节对齐一下为32,再加上isa的8个字节,最终结果为40malloc_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格式化输出: