0% found this document useful (0 votes)
30 views10 pages

Swift之内存布局

Uploaded by

张红波
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views10 pages

Swift之内存布局

Uploaded by

张红波
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 10

2021/3/31 swift之内存布局 -- www.zhifoubj.

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);

static const size_tUnownedRefCountShift=shiftAfterField(IsPinned);


static const size_tUnownedRefCountBitCount=31;
static const uint64_tUnownedRefCountMask=maskForField(UnownedRefCount);

static const size_tIsDeinitingShift=shiftAfterField(UnownedRefCount);


static const size_tIsDeinitingBitCount=1;
static const uint64_tIsDeinitingMask=maskForField(IsDeiniting);

static const size_tStrongExtraRefCountShift=shiftAfterField(IsDeiniting);


static const size_tStrongExtraRefCountBitCount=30;
static const uint64_tStrongExtraRefCountMask=maskForField(StrongExtraRefCoun
t);

static const size_tUseSlowRCShift=shiftAfterField(StrongExtraRefCount);


static const size_tUseSlowRCBitCount=1;
static const uint64_tUseSlowRCMask=maskForField(UseSlowRC);

static const size_tSideTableShift=0;


static const size_tSideTableBitCount=62;
static const uint64_tSideTableMask=maskForField(SideTable);
static const size_tSideTableUnusedLowBits=3;

static const size_tSideTableMarkShift=SideTableBitCount;


static const size_tSideTableMarkBitCount=1;
static const uint64_tSideTableMarkMask=maskForField(SideTableMark);
};
===============================
低位 》⾼位
---
1位 IsPinned
31位 UnownedRefCountBit
1位 IsDeiniting
30位 StrongExtraRefCount
1位 UseSlowRC
如果是有 sidetable 的情况,各个 表示如下:
bit
62位 SideTable 指针
1位 SideTableMark
1位 UseSlowRC

再看对象内存布局
再看看⼀开始的图:

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中)。

weak var p8=p1

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

enum Empty{}// => empty type


此时 size 0 为, alignment 1 为 , 为了对⻬strip为1
Single-Case Enums
如果enum只有⼀个case,那么关联了什么data就怎么布局,如果没有关联,那么为empty(因为只有
⼀个case不需要区分)。
enum EmptyCase{caseX} // => empty type
enum DataCase{caseY(Int,Double)}// => LLVM
为,
EmptyCasesize 0 alignment 1 strip 1 为, 为
DataCase 布局就是 个
1 Int,Double 。所以 为 ,
size 16 alignment 8 为 ,strip为16
C-Like Enums
如果所有case都没有关联data type,那么这就是⼀个c-like enum。enum布局就是⼀个整数tag,⽤最
少的bit来描述所有case。
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 7/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com

⾸⻚ enum EnumLike2{// => LLVM i1


case A // => i1 0
case B // => i1 1
}

enum EnumLike8{// => LLVM i3


case A // => i3 0
case B // => i3 1
case C // => i3 2
case D // etc.
case E
case F
case G
case H
}
为,
size 1 为, 为 。为什么是1呢,因为1个字节可以表示2的8次⽅种情况,⾜够
alignment 1 strip 1
表示这么多 case 了。那超过了呢?
⼜⽐如
enum A{
case A0
case A1
....
case A280
}
此时不⾜以⽤ 个字节表示所以,size为2,alignment为2,strip为2.
1

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}
}

IntOrInfinity.Int( 0)=>{ 0,0}


IntOrInfinity.Int(20721)=>{20721,0}
为, 为,
size 9 alignment 8 strip 16 为
Multi-Payload Enums
如果有⼤于1个case关联了data type,那么就是Multi-Payload Enums。此时也是⼀样的尽量共⽤空
间,⽆法共⽤时,增加bit进⾏区分。
https://www.zhifoubj.com/articleDetail/be7b24ef140834bcfe66f5be7990b1d6 8/11
2021/3/31 swift之内存布局 -- www.zhifoubj.com

⾸⻚ 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 为

Existential Container Layout


类型、组合协议类型、Any等这些⽆法确定⼤⼩的类型,他们都Existential Container。具有
protocol
同样的layout。
protocol
必须容纳任意⼤⼩和对⻬的值。此时使⽤3个指针⼤⼩的固定数据区。如果他的
Existential Containers
⼤⼩和对其都⼩于等于固定缓冲区的⼤⼩,则直接包含该值。如果不能包含,这存储⼀个指向其数据
的指针。具体是什么类型,由⼀个类型元数据记录标识。protocol的⽅法在witnesstable中。
struct OpaqueExistentialContainer{
void*fixedSizeBuffer[3];// 数据区
Metadata*type;// 数据区的类型
WitnessTable*witnessTables[NUM_WITNESS_TABLES];//protocol witnesstable 的
};

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

You might also like