iOS底层原理:OC对象底层探索之开辟内存(二)

简介: 简介: 在上篇文章iOS底层原理(二):OC对象底层探索之alloc初探 中,我们体验了 objc 底层源码的调试流程,也介绍了一部分 [JQPerson alloc] 在底层的工作流程,最终在callAlloc中走到了_objc_rootAllocWithZone方法。那么今天我们就来继续探索_objc_rootAllocWithZone方法之后的流程吧!
内存优化

看完了结构体的内存对齐,我们再来看一下OC对象的内存对齐又是怎样的呢?


image.png

JQPerson中自定义的变量和JQStruct2的成员的类型和顺序是一模模一样样的,他们打印出来的内存大小都是24字节,也是一模模一样样的,乍一看,没毛病呀。大哥,你忘记了对象本身自带了一个变量isa指针吗?它也占了8个字节呢。所以这样一看,JQPerson中自定义的变量只占了16个字节,这就很奇怪了啊,变量的类型和顺序都是一样的啊。这到底是为啥呢?这就是接下来我们要说的内存优化。

来,我们具体看看是它是怎么优化的,上代码:


image.png


通过lldb断点打印可以看出 :

  1. isa的值通过读取 0x0000000109dc6658 (8字节)这个内存的数据;
  2. a的值通过读取 0x4067200000000000 (8字节)这个内存的数据;
  3. b的值通过读取 0x00000012 (4字节)这个内存的数据,
  4. c的值通过读取 0x0002 (2字节)这个内存的数据,
  5. d的值通过读取 0x0063 (2字节)(内存对齐,高位补0,所以这里看着就是俩字节了)这个内存的数据,

我们发现 int b,short c,char d,共用了一个8字节内存空间,系统进行了内存优化,将对象的属性或者变量存储顺序进行了重排,达到内存占用最小。

上面这个例子理解起来可能还不够透彻,下面我们在JQPerson中在增加一些属性,打印一下内存数据,一看就明白了。


image.png

image.png

看图👆,没有文字了!!!

由此我们可以得出结论:

  • OC对象与对象之间是以16字节对齐的。
  • 对象内部的成员变量之间是以8字节对齐的(OC最大数据类型是8字节,而每个对象都自带了一个8字节的isa)。
calloc 开辟内存

上面我们讲到了instanceSize方法计算对象所需的内存大小,并且拓展了内存对齐内存优化。接着,我们就来看一下calloc是怎么去开辟内存的。

好,我们断点快速来到_class_createInstanceFromZone方法中


image.png


这里可以看出,此时的obj还没有进行赋值,就已经有地址了,说明系统给obj分配了一块内存地址(脏地址)。


image.png


接着走断点,看到:执行calloc后打印的是一个16进制的指针地址,说明已经开辟了内存,但是和平常见到的地址指针(下图👇)不一样。


image.png


也就是说,calloc只是开辟了内存;但是这块内存空间并没有和相应的类进行关联。

我们一步一步来看,先点击calloc,进去看看calloc都做了什么?


image.png


发现这里进不去了,calloc没有提供方法实现,好想进去啊,怎么办?

拿出我们上篇文章讲的三种底层调试方法,符号断点跟一下流程


image.png


看到这里calloc方法在libsystem_malloc.dylib中,这是个系统库啊。毫不犹豫,直接去 Open Source 下载libsystem_malloc.dylib的源码啊。(ps:其实上面已经指出了calloc的位置,macOS 11.3/usr/include/malloc,天哪,这明显是个系统库啊)

好,接下里,我们拿malloc的源码编译调试,看看calloc的做了啥。上代码


image.png

  • 进入 calloc


image.png

  • 进入 _malloc_zone_calloc

image.png

33.png

  • 进入 zone->calloc,发现点不进去,怎么办呢?首先想到的是汇编跟一下流程。但是注意,C语言指针->指向的就是函数的首地址,我们直接打印一下zone->calloc看看


image.png

  • 看到下一个跳转是default_zone_calloc,全局搜索 default_zone_calloc

image.png

卧槽,又来一个zone->calloc

  • 断点来到这里,继续打印一下zone->calloc看看


image.png

  • 看到下一个跳转是nano_calloc,全局搜索 nano_calloc


image.png

  • 断点发现会执行到_nano_malloc_check_clear,进入_nano_malloc_check_clear看一下


image.png

  • 继续进入segregated_size_to_fit看看


image.png

这里发现只有201、202这两句核心代码:

k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM;

这两句啥意思呢?我们看到 >><<这两个符号,这很熟悉了啊,这不就是右移和左移嘛!

我们分别点击宏NANO_REGIME_QUANTA_SIZESHIFT_NANO_QUANTUM进去看一下都代表什么


image.png

知道了宏的定义,我们就还原一下上面201、202那两句代码:

k = (size + (1 << 4) - 1) >> 4; 
slot_bytes = k << 4;


这就很清晰了,前面内存对齐时我们讲过, >> 4 再 << 4 得出的结果就是16的整数倍,(size + (1 << 4) - 1) = size + 15。意思就是先给size升到16(二进制10000)以上,不足的位全抹0。这就是16进制对齐的算法!

我们拿上面传的40计算一下:


image.png

OK,到此,我们calloc的底层就结束了。

下面继续完善一下alloc的流程图:

image.png


总结


  • 结构体是以其最大成员的字节数对齐的。
  • 对象内部的成员变量之间是以8字节对齐的。
  • OC对象与对象之间是以16字节对齐的。
  • instanceSize 是计算实例对象所需要的内存大小
  • calloc 是系统为对象开辟内存

下一篇,我们来介绍OC对象的最后一个内容:isa关联类。敬请期待~



相关文章
|
6月前
|
编译器 C语言 C++
【C语言】realloc()函数详解(动态内存开辟函数)
【C语言】realloc()函数详解(动态内存开辟函数)
85 0
|
6月前
|
编译器 C++
C/C++动态内存开辟(详解)
C/C++动态内存开辟(详解)
|
Unix 程序员 Linux
【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域
【OSTEP】动态内存开辟 | 内存API常见错误 | UNIX: brk/sbrk 系统调用 | mmap创建匿名映射区域 | mmap创建以文件为基础的映射区域
263 0
|
3月前
|
Swift iOS开发
iOS开发-属性的内存管理
【8月更文挑战第12天】在iOS开发中,属性的内存管理至关重要,直接影响应用性能与稳定性。主要策略包括:`strong`(强引用),不维持对象生命期,可用于解除循环引用;`assign`(赋值),适用于基本数据类型及非指针对象属性;`copy`,复制对象而非引用,确保对象不变性。iOS采用引用计数管理内存,ARC(自动引用计数)自动处理引用增减,简化开发。为避免循环引用,可利用弱引用或Swift中的`[weak self]`。最佳实践包括:选择恰当的内存管理策略、减少不必要的强引用、及时释放不再使用的对象、注意block内存管理,并使用Xcode工具进行内存分析。
|
5月前
|
C语言
C语言学习记录——动态内存开辟常见的错误
C语言学习记录——动态内存开辟常见的错误
33 1
|
6月前
|
编译器 C++
内存对齐与内存开辟。结构体(struct),位段,枚举类型(enum),联合体(union)。
内存对齐与内存开辟。结构体(struct),位段,枚举类型(enum),联合体(union)
36 1
|
5月前
|
C语言
动态内存开辟(下)
动态内存开辟(下)
22 0
|
5月前
|
编译器 C语言
动态内存开辟(上)
动态内存开辟(上)
24 0
|
6月前
关于动态开辟内存的经典笔试题
关于动态开辟内存的经典笔试题
28 0
|
6月前
|
编译器 C语言 C++
【C语言】calloc()函数详解(动态内存开辟函数)
【C语言】calloc()函数详解(动态内存开辟函数)
103 0