struct和tuple內(nèi)存布局
結(jié)構(gòu)體和元組當(dāng)前共享相同的布局算法,在編譯器實(shí)現(xiàn)中稱為“通用”布局算法。算法如下:
一開始設(shè)置size為0,alignment為1
-
遍歷字段,對于每個(gè)字段:
先根據(jù)這個(gè)字段的alignment來更新size,讓這個(gè)字段能夠?qū)R
size增加這個(gè)字段的大小
更新alignment為 Max(alignment,這個(gè)字段的alignment)
最終拿到總的size和alignment,然后size根據(jù)alignment對其,得到strip。
比如:
structPerson{
var age:Int64=0
var sex:UInt16=0
var address:Double=0.0
var name:UInt8=0
}
開始設(shè)置size=0,alignment=1
age是8字節(jié)對其,size為0,沒問題,不需要調(diào)整。
age是8字節(jié),size增加8
age的alignment為8,更新alignment為Max(1,8)
sex為2字節(jié)對其,size為8,沒問題,不用調(diào)整。
sex是2字節(jié),size增加2,為10
sex的alignment為2,更新alignment為Max(8,2)
address為8字節(jié)對其,size為10,需調(diào)整size為16,來保證對其。
address是8字節(jié),size增加8,為24
address的alignment為8,更新alignment為Max(8,8)
name為1字節(jié)對其,size為24,不用調(diào)整。
name是1字節(jié),size增加1,為25
name的alignment為1,更新alignment為Max(8,1)
所以size為25, alignment為8, 調(diào)整strip為32,保證對其。</pre>
Class Layout
參考https://academy.realm.io/posts/goto-mike-ash-exploring-swift-memory-layout/, 里面有一個(gè)探索內(nèi)存的工具https://github.com/mikeash/memorydumper2。我們傳一個(gè)變量給他,他能分析出這個(gè)是一個(gè)指針還是其他東西,并給出關(guān)系圖譜,不過需要安裝Graphviz。
class是引用類型,因此,我們定義一個(gè)變量拿到的是這個(gè)實(shí)例變量在內(nèi)存中的引用。
classPerson{
var age=0x0101010101010101
var money= 0x0202020202020202
}
letp=Person()
那么p指向的實(shí)例對象的內(nèi)存模式大概長什么樣呢?
現(xiàn)在還看不懂,我們先介紹下swift源碼中的一些數(shù)據(jù)結(jié)構(gòu)。
HeapObject
swift中所有分配在堆上的東西都是一個(gè)HeapObject。我們看看HeapObject的定義:
/** 我們看到其實(shí)就兩個(gè)變量,一個(gè)metadata,和一個(gè)InlineRefCounts */
struct HeapObject{
/// This is always a valid pointer to a metadata object.
HeapMetadataconst*metadata;
?
InlineRefCounts refCounts;
};
HeapMetadata
HeapMetadata是類結(jié)構(gòu)體的指針。
//===============HeapMetadata=============
template<typenameTarget>structTargetHeapMetadata;
usingHeapMetadata=TargetHeapMetadata<InProcess>;
//================TargetHeapMetadata==============
//繼承自TargetMetadata,并且有兩個(gè)初始化方法,通過Kind初始化和通過TargetAnyClassMetadata初始化
template<typenameRuntime>
structTargetHeapMetadata: TargetMetadata<Runtime>{
usingHeaderType=TargetHeapMetadataHeader<Runtime>;
?
TargetHeapMetadata() =default;
constexprTargetHeapMetadata(MetadataKindkind)
: TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
constexprTargetHeapMetadata(TargetAnyClassMetadata<Runtime>*isa)
: TargetMetadata<Runtime>(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)
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<typenameRuntime>
structTargetAnyClassMetadata: publicTargetHeapMetadata<Runtime>{
....
ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata>Superclass;
TargetPointer<Runtime, void>CacheData[2];
StoredSizeData;
.....
};
structTargetHeapMetadata: TargetMetadata<Runtime>{
....
};
structTargetMetadata{
//StoredPointer是不同平臺(tái)上指針大小的類型,32位上相當(dāng)于int32,64位上為int64,他要么是一個(gè)MetaKind枚舉,要么是一個(gè)isa指針。
StoredPointerKind;
}
綜上我們再看看TargetAnyClassMetadata都有些什么:
{
StoredPointerKind;
ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata>Superclass;
TargetPointer<Runtime, void>CacheData[2];
StoredSizeData;
}
熟悉么?沒什么感覺的話,我們再看看oc的class的定義:
structobjc_class{
ClassISA;//對應(yīng)Kind
Classsuperclass;//對應(yīng)Superclass
cache_tcache; //對應(yīng)cachedata
class_data_bits_tbits; //對應(yīng)Data
}
綜上,我們看到HeapMetadata就是一個(gè)類結(jié)構(gòu)體。代表這個(gè)實(shí)例的Class。HeapObject中的第一個(gè)變量
HeapMetadata const *metadata
就是一個(gè)指向Class對象的指針。
InlineRefCounts
typedef RefCounts<InlineRefCountBits>InlineRefCounts;
?
template<typenameRefCountBits>
classRefCounts{
std::atomic<RefCountBits>refCounts;
/** 各種操作引用計(jì)數(shù)的方法 */
incrementSlow()
tryIncrementAndPinSlow
decrementShouldDeinit
.....
}
從上可以看出InlineRefCounts就是InlineRefCountBits,并提供了各種操作引用計(jì)數(shù)的方法。
typedef RefCountBitsT<RefCountIsInline>InlineRefCountBits;
?
template<RefCountInlinednessrefcountIsInline>
classRefCountBitsT{
//可能包含的是引用計(jì)數(shù),也可能包含的是sidetable的指針
BitsTypebits;
RefCountBitsT(HeapObjectSideTableEntry*side)
RefCountBitsT(uint32_tstrongExtraCount, uint32_tunownedCount);
//封裝了對各個(gè)bit的存儲(chǔ)的操作
getUnownedRefCount
getIsPinned
getIsDeiniting
。。。。。
}
從上可以看到,refCounts是一個(gè)RefCountBitsT類,類中有一個(gè)bits。如果沒有sidetable的情況,那么引用計(jì)數(shù)會(huì)記錄在這里面,如果有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(StrongExtraRefCount);
?
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的情況,各個(gè)bit表示如下:
62位 SideTable指針
1位 SideTableMark
1位 UseSlowRC
再看對象內(nèi)存布局
再看看一開始的圖:
E87b5600100000:是Class的地址。
02000000000000:是inlineref。引用計(jì)數(shù),不過怎么都和看到的offset對不上。02那個(gè)是00000010,其中的1是代表unowned ref的數(shù)量,我們可以驗(yàn)證下:
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個(gè)額外強(qiáng)引用,2個(gè)額外無主引用(總數(shù)為2+1,因?yàn)槌跏贾禐?),雖然不知道具體的offset規(guī)則,不過我們可以肯定,一定有一個(gè)1010和一個(gè)11子序列,代表5個(gè)額外強(qiáng)引用和3個(gè)無主引用:
可以看到06(00000110),其中11位無主引用數(shù)3,0a(00001010),其中101為額外強(qiáng)引用計(jì)數(shù)。
ok,我們在試試定義一個(gè)weak,讓對象有sidetable(因?yàn)閣eak引用計(jì)數(shù)在sidetable的SideTableRefCountBits中)。
weak var p8=p1
可以看到ref已經(jīng)變味一個(gè)指針+flag。
Class的layout
這是Person這個(gè)Class中的信息,讀取的信息描述也不是很清晰,我們大概可以認(rèn)為他是一個(gè)特殊的vtable,混雜了一些oc類結(jié)構(gòu)體中的一些信息。
Class{
isa
supperclass
cache
各種method指針
}
這是放大的部分,128字節(jié)開始是sayHello函數(shù),136字節(jié)開始是sayWorld函數(shù),還有各種getter和setter等。
第8字節(jié)開始,也就數(shù)superclass,可以看到,我們定義的Person是SwiftObject的子類。SwiftObject是一個(gè)實(shí)現(xiàn)了NSObject協(xié)議的OC類。
枚舉的內(nèi)存布局
在layout枚舉時(shí),ABI為了避免浪費(fèi)空間,會(huì)從以下的5種策略中選擇。
Empty Enums
enum Empty{}// => empty type
此時(shí)size為0, alignment為1, 為了對齊strip為1
Single-Case Enums
如果enum只有一個(gè)case,那么關(guān)聯(lián)了什么data就怎么布局,如果沒有關(guān)聯(lián),那么為empty(因?yàn)橹挥幸粋€(gè)case不需要區(qū)分)。
enum EmptyCase{caseX} // => empty type
enum DataCase{caseY(Int,Double)}// => LLVM <{ i64, double }>
EmptyCasesize為0,alignment為1,strip為1
DataCase布局就是1個(gè)Int,Double。所以size為16,alignment為8,strip為16
C-Like Enums
如果所有case都沒有關(guān)聯(lián)data type,那么這就是一個(gè)c-like enum。enum布局就是一個(gè)整數(shù)tag,用最少的bit來描述所有case。
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, alignment為1,strip為1。為什么是1呢,因?yàn)?個(gè)字節(jié)可以表示2的8次方種情況,足夠表示這么多case了。那超過了呢?
又比如
enum A{
case A0
case A1
....
case A280
}
此時(shí)不足以用1個(gè)字節(jié)表示所以,size為2,alignment為2,strip為2.
Single-Payload Enums
如果enum總有多個(gè)case,但是只有一個(gè)關(guān)聯(lián)了data type,其他都沒有,我們稱這種情況為single-payload enum。此時(shí)的原則就是盡量共用空間,無法共用時(shí),增加額外的位來區(qū)分情況。
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字節(jié)能表示的訪問,就在末尾增加一個(gè)字節(jié)來區(qū)分就是是有data還是沒data。
enum IntOrInfinity{=>LLVM<{i64,i1增加一位區(qū)分有沒datatype}>
case NegInfinity =><{i64,i1}>{ 0,1}
case Int(Int) =><{i64,i1}>{%Int,0}
case PosInfinity =><{i64,i1}>{ 1,1}
}
?
IntOrInfinity.Int( 0)=><{i64,i1}>{ 0,0}
IntOrInfinity.Int(20721)=><{i64,i1}>{20721,0}
size為9,alignment為8,strip為16
Multi-Payload Enums
如果有大于1個(gè)case關(guān)聯(lián)了data type,那么就是Multi-Payload Enums。此時(shí)也是一樣的盡量共用空間,無法共用時(shí),增加bit進(jìn)行區(qū)分。
class Bignum{}
?
enum IntDoubleOrBignum{=>LLVM<{i64,i2增加兩位區(qū)分3中情況 }>
case Int(Int) =><{i64,i2}>{ %Int, 0}
case Double(Double) =><{i64,i2}>{(bitcast %Doubletoi64),1}
case Bignum(Bignum) =><{i64,i2}>{(ptrtoint%Bignumtoi64),2}
}
size為9,alignment為8,strip為16
Existential Container Layout
protocol類型、組合協(xié)議類型、Any等這些無法確定大小的類型,他們都Existential Container。具有同樣的layout。
protocol
Existential Containers必須容納任意大小和對齊的值。此時(shí)使用3個(gè)指針大小的固定數(shù)據(jù)區(qū)。如果他的大小和對其都小于等于固定緩沖區(qū)的大小,則直接包含該值。如果不能包含,這存儲(chǔ)一個(gè)指向其數(shù)據(jù)的指針。具體是什么類型,由一個(gè)類型元數(shù)據(jù)記錄標(biāo)識(shí)。protocol的方法在witnesstable中。
struct OpaqueExistentialContainer{
void*fixedSizeBuffer[3];//數(shù)據(jù)區(qū)
Metadata*type;//數(shù)據(jù)區(qū)的類型
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")
就兩個(gè)Int的空間。ok,現(xiàn)在我們用Existential Container來存儲(chǔ)他:
let p:P=StructSmallP()//定義為P
dumpAndOpenGraph(dumping:p,maxDepth:4,filename:"StructSmallP")
前兩個(gè)為struct的a和b。前3個(gè)緩沖區(qū)剩余空間會(huì)用于存放valuewitnesstable中的一些方法,包含一些初始化函數(shù),如果沒空間了就沒有這個(gè)。第四個(gè)為type,第5個(gè)為protocol witnesstable。正如我們最開始看到的那樣:3個(gè)緩沖區(qū),一個(gè)type,一個(gè)pwt。
AnyObject
AnyObject由于對象都是指針,所以不會(huì)存在大小不一致的情況。
let objs=[ClassA(),ClassB(),ClassC()]
Any
和protocol類似,只是沒有了protocol witness table。
struct OpaqueExistentialContainer{
void*fixedSizeBuffer[3];//數(shù)據(jù)區(qū)
Metadata*type;//數(shù)據(jù)區(qū)的類型
};