struct和tuple内存布局
结构体和元组当前共享相同的布局算法,在编译器实现中称为“通用”布局算法。算法如下:
- 一开始设置size为0,alignment为1
- 遍历字段,对于每个字段:
- 先根据这个字段的alignment来更新size,让这个字段能够对齐
- size增加这个字段的大小
- 更新alignment为 Max(alignment,这个字段的alignment)
- 最终拿到总的size和alignment,然后size根据alignment对其,得到strip。
比如:
|
|
Class Layout
参考https://academy.realm.io/posts/goto-mike-ash-exploring-swift-memory-layout/, 里面有一个探索内存的工具https://github.com/mikeash/memorydumper2。我们传一个变量给他,他能分析出这个是一个指针还是其他东西,并给出关系图谱,不过需要安装Graphviz。
class是引用类型,因此,我们定义一个变量拿到的是这个实例变量在内存中的引用。
|
|
那么p指向的实例对象的内存模式大概长什么样呢?
现在还看不懂,我们先介绍下swift源码中的一些数据结构。
HeapObject
swift中所有分配在堆上的东西都是一个HeapObject。我们看看HeapObject的定义:
|
|
HeapMetadata
HeapMetadata是类结构体的指针。
|
|
综上,我们看到HeapMetadata就是一个类结构体。代表这个实例的Class。HeapObject中的第一个变量
HeapMetadata const *metadata
就是一个指向Class对象的指针。
InlineRefCounts
|
|
从上可以看出InlineRefCounts就是InlineRefCountBits,并提供了各种操作引用计数的方法。
|
|
从上可以看到,refCounts是一个RefCountBitsT类,类中有一个bits。如果没有sidetable的情况,那么引用计数会记录在这里面,如果有sidetable,那么包含的是HeapObjectSideTableEntry的指针。
|
|
再看对象内存布局
再看看一开始的图:
E87b5600100000:是Class的地址。
02000000000000:是inlineref。引用计数,不过怎么都和看到的offset对不上😆。02那个是00000010,其中的1是代表unowned ref的数量,我们可以验证下:
|
|
上面有5个额外强引用,2个额外无主引用(总数为2+1,因为初始值为1),虽然不知道具体的offset规则,不过我们可以肯定,一定有一个1010和一个11子序列,代表5个额外强引用和3个无主引用:
可以看到06(00000110),其中11位无主引用数3,0a(00001010),其中101为额外强引用计数。
ok,我们在试试定义一个weak,让对象有sidetable(因为weak引用计数在sidetable的SideTableRefCountBits中)。
|
|
可以看到ref已经变味一个指针+flag。
Class的layout
这是Person这个Class中的信息,读取的信息描述也不是很清晰,我们大概可以认为他是一个特殊的vtable,混杂了一些oc类结构体中的一些信息。
|
|
这是放大的部分,128字节开始是sayHello函数,136字节开始是sayWorld函数,还有各种getter和setter等。
第8字节开始,也就数superclass,可以看到,我们定义的Person是SwiftObject的子类。SwiftObject是一个实现了NSObject协议的OC类。
枚举的内存布局
在layout枚举时,ABI为了避免浪费空间,会从以下的5种策略中选择。
Empty Enums
|
|
Single-Case Enums
如果enum只有一个case,那么关联了什么data就怎么布局,如果没有关联,那么为empty(因为只有一个case不需要区分)。
|
|
C-Like Enums
如果所有case都没有关联data type,那么这就是一个c-like enum。enum布局就是一个整数tag,用最少的bit来描述所有case。
|
|
Single-Payload Enums
如果enum总有多个case,但是只有一个关联了data type,其他都没有,我们称这种情况为single-payload enum。此时的原则就是尽量共用空间,无法共用时,增加额外的位来区分情况。
|
|
|
|
Multi-Payload Enums
如果有大于1个case关联了data type,那么就是Multi-Payload Enums。此时也是一样的尽量共用空间,无法共用时,增加bit进行区分。
|
|
Existential Container Layout
protocol类型、组合协议类型、Any等这些无法确定大小的类型,他们都Existential Container。具有同样的layout。
protocol
Existential Containers必须容纳任意大小和对齐的值。此时使用3个指针大小的固定数据区。如果他的大小和对其都小于等于固定缓冲区的大小,则直接包含该值。如果不能包含,这存储一个指向其数据的指针。具体是什么类型,由一个类型元数据记录标识。protocol的方法在witnesstable中。
|
|
|
|
就两个Int的空间。ok,现在我们用Existential Container来存储他:
|
|
前两个为struct的a和b。前3个缓冲区剩余空间会用于存放valuewitnesstable,包含一些初始化函数,如果没空间了就没有这个。第四个为type,第5个为protocol witnesstable。正如我们最开始看到的那样:3个缓冲区,一个type,一个pwt。
AnyObject
AnyObject由于对象都是指针,所以不会存在大小不一致的情况。
|
|
Any
和protocol类似,只是没有了protocol witness table。
|
|