01.alloc与字节对齐

一、探索alloc

看下面的例子,输出会是什么样的呢?

Ryukie *obj1 = [Ryukie alloc];
Ryukie *obj2 = [obj1 init];
Ryukie *obj3 = [obj1 init];

NSLog(@"Objc1:%p,他的指针:%p", obj1, &obj1);
NSLog(@"Objc2:%p,他的指针:%p", obj2, &obj2);
NSLog(@"Objc3:%p,他的指针:%p", obj3, &obj3);

输出如下:

2021-06-06 19:56:22.045515+0800 OCObject[10944:3914848] Objc1:0x28220bc60,他的指针:0x16d421bf8
2021-06-06 19:56:22.045611+0800 OCObject[10944:3914848] Objc2:0x28220bc60,他的指针:0x16d421bf0
2021-06-06 19:56:22.045659+0800 OCObject[10944:3914848] Objc3:0x28220bc60,他的指针:0x16d421be8
  • 不同的指针,指向了同样的内存空间

二、通过源码了解alloc实现

Source Browser

Apple Open Source

NSObject.mm这里就是NSobject的核心实现代码

2.1 可运行调试的源码

GitHub

将项目Clone下来,即可运行调试。更加直观的学习底层实现。

2.2 调用流程探究

a. 实验代码

我们在main中写下如下代码,分别在<-处添加断点

b. alloc

现在是RYModel第一次调用alloc。字面实现如下:

按照代码下一步应该会调用_objc_rootAlloc,真的吗?

c. objc_alloc

我们发现断点走到了这里,正如注释里面所说:Calls [cls alloc].

我们推断,这里应该是alloc的实现IMP被修改了。继续下一步

d. callAlloc

断点直接来到了 objc_msgSend 处,又继续调用了 alloc

e. _objc_rootAlloc

这次断点才来到了,_objc_rootAlloc

f. _objc_rootAllocWithZone

又来到了callAlloc方法中,这次断点来到了_objc_rootAllocWithZone

_objc_rootAllocWithZone的实现:

我们继续下一步

g. _class_createInstanceFromZone 创建实例

g.1 instanceSize 大小

  • 这里会去缓存中查找

  • CoreFoundation规定最小16个字节

    • isa指针的8字节

    • 还有8字节预留

  • // Class's ivar size rounded up to a pointer-size boundary.

    • 成员变量的大小向上取整(一个指针的大小8字节)

    • 即:成员变量的字节对齐

    • 即:不足8字节就按8字节

  • // May be unaligned depending on class's ivars.

    • 大小取决于成员变量的大小

g.2 calloc 开空间

这里分配的还空间内还有数据,因为内存只覆写,不删除。

g.3 initInstanceIsa 初始化实例isa

这里会将刚创建的obj和isa进行绑定,完成这一步后,可以发现,obj就会先变成RYModel了。

h. 非第一次alloc

  • 发现在d. callAlloc这一步中有所不同:

  • 没有走return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));

  • 而是直接走了_objc_rootAllocWithZone

这里是LLVM做了处理,对alloc的IMP做了调整,在类第一次调用alloc时进行了一些处理,然后再调用原有的alloc实现。

i. 流程图

图片较大较长,建议下载到本地

三、字节对齐

3.1 算法

3.2 先思考一个问题:

6
  • 显然第二种效率更高,但是牺牲了一定的空间。

    • 这就是典型的以空间,换时间,对于CPU来说处理速度非常重要

3.3 内存调试

  • 我们在第一个demo中下个断点

7
  • (long) $1 = 80361110149439809这里其实是isa指针,但是打印出来确实long类型?

3.4 isa 与 MASK

a.获取isa指针

这里要拿到isa需要&一个mask,在isa.h中可以找到对应架构的定义

b.获取MASk

下面是ARM64的,其他架构的可以自行在头文件中查找,很简单的

c.复原出isa

再次输出

成功输出了Class: Ryukie

3.5 查看真实的内存排列方式

a.一个内存对齐的场景

  • 首先我们格式化打印分析 x/4gx

  • 这种方式更加直观,不用我们自己去按小端的方式反着看了。

    • 4代表打印4个单位的,可以按需要调整。

  • 这里发现 po 0x0000001200000001 结果很怪异 77309411329

  • 这里我们还剩 age & isboy 两个成员变量没有输出

  • 将这里的16位分开看下

    • po 0x00000012

      • 18

    • po 0x00000001

      • 1

      • 这里Bool只占一个字节但是拉伸为了8个字节

  • 这里就是比较明显的内存对齐的一个场景

b.思考

如果我们将上面的@property (nonatomic, assign) int age;改为@property (nonatomic, assign) NSInteger age; 会是怎样呢?

Last updated

Was this helpful?