Swift之内存布局
Swift之内存布局
com
⾸⻚
swift之内存布局 推荐⽂章
xingou
2020-11-27 6:51:3 被扫码⽀配的中⽼年⼈
会」的权利?
struct tuple 和 内存布局 熬夜再晚也不怕,抹上
“发光肌”
结构体和元组当前共享相同的布局算法,在编译器实现中称为“通⽤”布局算法。算法如下:
⼀开始设置size为0,alignment为1 为什么你养的仙⼈掌都
遍历字段,对于每个字段: 票房|国庆档票房39.5
国庆档票房第⼆ “提振
先根据这个字段的alignment来更新size,让这个字段能够对⻬
size增加这个字段的⼤⼩
重磅|众头部主播集体
区团灭!CSGO失守!
更新alignment为 Max(alignment,这个字段的alignment)
最终拿到总的size和alignment,然后size根据alignment对其,得到strip。
⽐如:
structPerson{
var age:Int64=0
var sex:UInt16=0
var address:Double=0.0
var name:UInt8=0
}
开始设置size=0,alignment=1
age是8字节对其,size为0,没问题,不需要调整。
age是8字节,size增加8
age的alignment为8,更新alignment为Max(1,8)
sex为2字节对其,size为8,没问题,不⽤调整。
sex是2字节,size增加2,为10
sex的alignment为2,更新alignment为Max(8,2)
address为8字节对其,size为10,需调整size为16,来保证对其。
address是8字节,size增加8,为24
address的alignment为8,更新alignment为Max(8,8)
name为1字节对其,size为24,不⽤调整。
name是1字节,size增加1,为25
name的alignment为1,更新alignment为Max(8,1)
所以size为25, alignment为8, 调整strip为32,保证对其。
Class Layout
参考https://academy.realm.io/posts/goto-mike-ash-exploring-swift-memory-layout/, ⾥⾯有⼀个探
索内存的⼯具https://github.com/mikeash/memorydumper2。我们传⼀个变量给他,他能分析出这个
是⼀个指针还是其他东⻄,并给出关系图谱,不过需要安装Graphviz。
class是引⽤类型,因此,我们定义⼀个变量拿到的是这个实例变量在内存中的引⽤。
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 1/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com
⾸⻚ classPerson{
var age=0x0101010101010101
var money= 0x0202020202020202
}
letp=Person()
那么p指向的实例对象的内存模式⼤概⻓什么样呢?
image
现在还看不懂,我们先介绍下swift源码中的⼀些数据结构。
HeapObject
swift 中所有分配在堆上的东⻄都是⼀个HeapObject。我们看看HeapObject的定义:
/** 我们看到其实就两个变量,⼀个 metadata ,和⼀个
InlineRefCounts */
struct HeapObject{
/// This is always a valid pointer to a metadata object.
HeapMetadataconst*metadata;
InlineRefCounts refCounts;
};
HeapMetadata
HeapMetadata 是类结构体的指针。
//===============HeapMetadata=============
templatestructTargetHeapMetadata;
usingHeapMetadata=TargetHeapMetadata;
//================TargetHeapMetadata==============
//继承⾃ TargetMetadata ,并且有两个初始化⽅法,通过 Kind 初始化和通过TargetAnyClassMeta
初始化
data
template
structTargetHeapMetadata: TargetMetadata{
usingHeaderType=TargetHeapMetadataHeader;
TargetHeapMetadata() =default;
constexprTargetHeapMetadata(MetadataKindkind)
: TargetMetadata(kind) {}
#if SWIFT_OBJC_INTEROP
constexprTargetHeapMetadata(TargetAnyClassMetadata*isa)
: TargetMetadata(isa) {}
#endif
};
//=================Kind==========
enumclassMetadataKind: uint32_t{
#define METADATAKIND(name, value) name = value,
#define ABSTRACTMETADATAKIND(name, start, end)
\
name##_Start = start, name##_End = end,
#include "MetadataKind.def"
/** 这是从MetadataKind.def 截取的⼀段,可以看到就是声明了各种 Kind 的枚举
NOMINALTYPEMETADATAKIND(Struct, 1)
NOMINALTYPEMETADATAKIND(Enum, 2)
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 2/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com
⾸⻚
NOMINALTYPEMETADATAKIND(Optional, 3)
METADATAKIND(Opaque, 8)
METADATAKIND(Tuple, 9)
METADATAKIND(Function, 10)
METADATAKIND(Existential, 12)
METADATAKIND(Metatype, 13)
METADATAKIND(ObjCClassWrapper, 14)*/
LastEnumerated=2047,
};
//================TargetAnyClassMetadata==========
//
template
structTargetAnyClassMetadata: publicTargetHeapMetadata{
....
ConstTargetMetadataPointerSuperclass;
TargetPointerCacheData[2];
StoredSizeData;
.....
};
structTargetHeapMetadata: TargetMetadata{
....
};
structTargetMetadata{
//StoredPointer 是不同平台上指针⼤⼩的类型, 位上相当于 32 int32 ,64位上为int64,他要么
是⼀个 MetaKind 枚举,要么是⼀个 指针。
isa
StoredPointerKind;
}
综上我们再看看 TargetAnyClassMetadata 都有些什么:
{
StoredPointerKind;
ConstTargetMetadataPointerSuperclass;
TargetPointerCacheData[2];
StoredSizeData;
}
熟悉么?没什么感觉的话,我们再看看 的 oc class 的定义:
structobjc_class{
对应
ClassISA;// Kind
对应
Classsuperclass;// Superclass
对应
cache_tcache; // cachedata
对应
class_data_bits_tbits; // Data
}
综上,我们看到HeapMetadata就是⼀个类结构体。代表这个实例的Class。HeapObject中的第⼀个变
量HeapMetadata const *metadata就是⼀个指向Class对象的指针。
InlineRefCounts
typedef RefCountsInlineRefCounts;
template
classRefCounts{
std::atomicrefCounts;
/** 各种操作引⽤计数的⽅法 */
incrementSlow()
tryIncrementAndPinSlow
decrementShouldDeinit
.....
}
从上可以看出InlineRefCounts就是InlineRefCountBits,并提供了各种操作引⽤计数的⽅法。
typedef RefCountBitsTInlineRefCountBits;
template
classRefCountBitsT{
// 可能包含的是引⽤计数,也可能包含的是sidetable的指针
BitsTypebits;
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 3/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com
⾸⻚
RefCountBitsT(HeapObjectSideTableEntry*side)
RefCountBitsT(uint32_tstrongExtraCount, uint32_tunownedCount) ;
//封装了对各个 的存储的操作
bit
getUnownedRefCount
getIsPinned
getIsDeiniting
。。。。。
}
从上可以看到,refCounts是⼀个RefCountBitsT类,类中有⼀个bits。如果没有sidetable的情况,那
么引⽤计数会记录在这⾥⾯,如果有sidetable,那么包含的是HeapObjectSideTableEntry的指针。
// 看看每⼀位表示什么 位情况(64 )
template<>
structRefCountBitOffsets<8>{
static const size_t IsPinnedShift=0;
static const size_tIsPinnedBitCount=1;
static const uint64_tIsPinnedMask=maskForField(IsPinned);
再看对象内存布局
再看看⼀开始的图:
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 4/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com
⾸⻚
image
E87b5600100000:是Class的地址。
02000000000000:是inlineref。引⽤计数,不过怎么都和看到的offset对不上。02那个是00000010,
其中的1是代表unowned ref的数量,我们可以验证下:
class Person{
var age=0x0102030405060708
var money= 0x0202020202020202
func sayHello() {
print("hello")
}
func sayWorld() {
print("world")
}
}
let p1=Person()
let p2=p1
let p3=p1
let p4=p1
let p5=p1
unowned var p6=p1
unowned var p7=p1
dumpAndOpenGraph(dumping:p1,maxDepth:10,filename:"SimpleClassPerson")
上⾯有5个额外强引⽤,2个额外⽆主引⽤(总数为2+1,因为初始值为1),虽然不知道具体的offset规
则,不过我们可以肯定,⼀定有⼀个1010和⼀个11⼦序列,代表5个额外强引⽤和3个⽆主引⽤:
image
可以看到06(00000110),其中11位⽆主引⽤数3,0a(00001010),其中101为额外强引⽤计数。
ok,我们在试试定义⼀个weak,让对象有sidetable(因为weak引⽤计数在sidetable的
SideTableRefCountBits中)。
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 5/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com
⾸⻚
image
可以看到ref已经变味⼀个指针+flag。
Class的layout
image
这是Person这个Class中的信息,读取的信息描述也不是很清晰,我们⼤概可以认为他是⼀个特殊的
vtable,混杂了⼀些oc类结构体中的⼀些信息。
Class{
isa
supperclass
cache
各种method 指针
}
image
这是放⼤的部分,128字节开始是sayHello函数,136字节开始是sayWorld函数,还有各种getter和
setter等。
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 6/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com
⾸⻚
image
image
第8字节开始,也就数superclass,可以看到,我们定义的Person是SwiftObject的⼦类。SwiftObject
是⼀个实现了NSObject协议的OC类。
枚举的内存布局
在layout枚举时,ABI为了避免浪费空间,会从以下的5种策略中选择。
Empty Enums
Single-Payload Enums
如果enum总有多个case,但是只有⼀个关联了data type,其他都没有,我们称这种情况为single-
payload enum。此时的原则就是尽量共⽤空间,⽆法共⽤时,增加额外的位来区分情况。
32位⾜够描述所有情况,就⽤这么多
enum CharOrSectionMarker{//=> LLVM i32
case Paragraph // => i32 0x0020_0000
case Char(UnicodeScalar)// => i32 (zext i21 %Char to i32)
case Chapter // => i32 0x0020_0001
}
CharOrSectionMarker.Char('\x00')=>i320x0000_0000
CharOrSectionMarker.Char('\u10FFFF')=>i320x0010_FFFF
enum CharOrSectionMarkerOrFootnoteMarker{=>LLVMi32
case CharOrSectionMarker(CharOrSectionMarker)=>i32%CharOrSectionMarker
case Asterisk =>i320x0020_0002
case Dagger =>i320x0020_0003
case DoubleDagger =>i320x0020_0004
}
不够了,总的情况为Int描述的+2,超出了Int8字节能表示的访问,就在末尾增加⼀个字节来区分就
//
是是有data还是没data。
enum IntOrInfinity{=>LLVM
case NegInfinity =>{ 0,1}
case Int(Int) =>{%Int,0}
case PosInfinity =>{ 1,1}
}
⾸⻚ class Bignum{}
enum IntDoubleOrBignum{=>LLVM
case Int(Int) =>{ %Int, 0}
case Double(Double) =>{(bitcast %Doubletoi64),1}
case Bignum(Bignum) =>{(ptrtoint%Bignumtoi64),2}
}
为, 为,
size 9 alignment 8 strip 16 为
struct StructSmallP:P{
func f(){}
func g(){}
func h(){}
vara=0x6c6c616d73
varb=0x6c6c616d73
}
letp=StructSmallP()
dumpAndOpenGraph(dumping:p,maxDepth:4,filename:"StructSmallP")
image
就两个Int的空间。ok,现在我们⽤Existential Container来存储他:
let p:P=StructSmallP()// P 定义为
dumpAndOpenGraph(dumping:p,maxDepth:4,filename:"StructSmallP")
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 9/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com
⾸⻚
image
image
前两个为struct的a和b。前3个缓冲区剩余空间会⽤于存放valuewitnesstable中的⼀些⽅法,包含⼀些
初始化函数,如果没空间了就没有这个。第四个为type,第5个为protocol witnesstable。正如我们最
开始看到的那样:3个缓冲区,⼀个type,⼀个pwt。
AnyObject
AnyObject 由于对象都是指针,所以不会存在⼤⼩不⼀致的情况。
let objs=[ClassA(),ClassB(),ClassC()]
Any
和 protocol 类似,只是没有了protocol witness table。
struct OpaqueExistentialContainer{
void*fixedSizeBuffer[3];// 数据区
Metadata*type;// 数据区的类型
};
image
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 10/11