iOS底层学习-内存对齐

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

一、对象的内存空间

首先我们接上一篇所用的objc源码,打上断点运行。在控制台输入x p,此时可以看到p对象的内存情况。
图片[1]-iOS底层学习-内存对齐-一一网
0x10066e1d0为首地址,后面则是内存。如何读取内存呢?由于iOS是小端模式,我们截取8位从后往前读取,在控制台打印出来。
图片[2]-iOS底层学习-内存对齐-一一网
打印出来的应该是isa是首地址,但是这里看起来并不是。因为这里需要再& ISA_MASK,在源码中找到在x86_64ISA_MASK值为0x00007ffffffffff8& ISA_MASK后打印出正确的isa
图片[3]-iOS底层学习-内存对齐-一一网
isa后面一堆00是对象所有属性的存储空间。

我们还是有另一种方式查看内存情况,点击菜单Debug->Debug Workflow->View Memory。然后在Address中输入首地址,这样也可以看到内存分布情况,即使对象属性没有赋值也会开辟内存。

下面我们给对象属性赋值后看下内存情况
图片[4]-iOS底层学习-内存对齐-一一网
height属性由long改为BOOL再看下内存情况
图片[5]-iOS底层学习-内存对齐-一一网
图片[6]-iOS底层学习-内存对齐-一一网
此时和上面的截图对比,我们发现ageheight在一起。这就是今天要探讨的内存对齐

二、对象内存影响因素

对象内存的影响因素有哪些?我们先在控制台打印一下对象内存大小
图片[7]-iOS底层学习-内存对齐-一一网
删除部分属性后再打印,发现属性会影响内存大小
图片[8]-iOS底层学习-内存对齐-一一网
此时在声明文件加上一个成员变量后再打印,发现成员变量也会影响
图片[9]-iOS底层学习-内存对齐-一一网
最后我们再添加方法看下,内存大小不受影响(类方法也是如此)

三、结构体内存对齐

各类型所占字节大小

内存对齐原则

1、数据成员对⻬规则:

结构struct(或联合union)的数据成员,第⼀个数据成员放在offset0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int4字节,则要从的整数倍地址开始存储)。

2、结构体作为成员:

如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储。(struct a⾥存有struct bb⾥有char int double等元素,那b应该从8的整数倍开始存储。)

3、收尾⼯作:

结构体的总⼤⼩,也就是sizeof的结果,必须是其内部最⼤成员的整数倍,不⾜的要补⻬。

结构体示例

先来看如下代码,struct1struct2打印出的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
图片[10]-iOS底层学习-内存对齐-一一网
图片[11]-iOS底层学习-内存对齐-一一网
打印结果为<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
图片[12]-iOS底层学习-内存对齐-一一网
我们需要去苹果开源库下载libmalloc源码

五、malloc分析探索

malloc_size查看系统分配内存大小。根据上一篇alloc探索可知先由calloc开辟内存空间,我们先看下calloc的实现
图片[13]-iOS底层学习-内存对齐-一一网
图片[14]-iOS底层学习-内存对齐-一一网
跟进_malloc_zone_calloc查看实现方法
图片[15]-iOS底层学习-内存对齐-一一网
在实现方法中有一行关键的代码,但是走到这里我们发现这行代码的calloc再点击只有声明没有实现

ptr = zone->calloc(zone, num_items, size);
复制代码

此时在这行代码下个断点,断住后control + step into跟进去(也可po zone->calloc查看(有赋值就有存储值)),进入到default_zone_calloc方法
图片[16]-iOS底层学习-内存对齐-一一网
用同样方法继续往下跟进去,进入nano_calloc方法
图片[17]-iOS底层学习-内存对齐-一一网
这里定位到一行关键代码,进入_nano_malloc_check_clear

void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
复制代码

图片[18]-iOS底层学习-内存对齐-一一网
_nano_malloc_check_clear的实现中看到我们关心的内存大小size_t这行代码,这里接收了我们最开始传入的size: 40,得到结果slot_bytes48。所以关键在segregated_size_to_fit这个方法,进去瞅一眼
图片[19]-iOS底层学习-内存对齐-一一网
图片[20]-iOS底层学习-内存对齐-一一网
我们发现这里对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格式化输出:

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享