前言
在探究OC底層源碼的時(shí)候,涉及到了聯(lián)合體和位域相關(guān)知識(shí)儡羔。比如objc_objct里面的isa_t就是一個(gè)聯(lián)合體等缀,isa_t里面就有位域類型的數(shù)據(jù)。今天我們就來好好學(xué)習(xí)一下位域祭陷。
簡(jiǎn)介
??位域哑蔫,也稱位段闸迷,在C語言中师溅,位段的聲明和結(jié)構(gòu)(struct)類似膝舅,但它的成員是一個(gè)或多個(gè)位的字段颠毙,這些不同長(zhǎng)度的字段實(shí)際儲(chǔ)存在一個(gè)或多個(gè)整型變量中。在聲明時(shí)蛀蜜,位段成員必須是整形或枚舉類型(通常是無符號(hào)類型)刻两,且在成員名的后面是一個(gè)冒號(hào)和一個(gè)整數(shù),整數(shù)規(guī)定了成員所占用的位數(shù)滴某。
??位域不能是靜態(tài)類型磅摹。不能使用&
對(duì)位域做取地址運(yùn)算,因此不存在位域的指針霎奢,編譯器通常不支持位域的引用(reference)户誓。
以下程序則展示了一個(gè)位段的聲明:
struct CHAR
{
unsigned int ch : 8; //8位
unsigned int font : 6; //6位
unsigned int size : 18; //18位
};
struct CHAR ch1;
以下程序展示了一個(gè)結(jié)構(gòu)體的聲明:
struct CHAR2
{
unsigned char ch; //8位
unsigned char font; //8位
unsigned int size; //32位
};
struct CHAR2 ch2;
??第一個(gè)聲明取自一段文本格式化程序,應(yīng)用了位段聲明幕侠。它可以處理256個(gè)不同的字符(8位)帝美,64種不同字體(6位),以及最多262,144個(gè)單位的長(zhǎng)度(18位)晤硕。這樣悼潭,在ch1這個(gè)字段對(duì)象中,一共才占據(jù)了32位的空間舞箍。而第二個(gè)程序利用結(jié)構(gòu)體進(jìn)行聲明舰褪,可以看出,處理相同的數(shù)據(jù)创译,CHAR2類型占用了48位空間抵知,如果考慮邊界對(duì)齊并把要求最嚴(yán)格的int類型最先聲明進(jìn)行優(yōu)化,那么CHAR2類型則要占據(jù)64位的空間软族。
實(shí)例分析
下面的demo例子輸出什么
struct BitFields {
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 4;
unsigned int bit4 : 2;
};
union my_isa_t {
unsigned int bitfields;
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 4;
unsigned int bit4 : 2;
};
union my_new_isa_t {
unsigned int bitfields;
struct {
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 4;
unsigned int bit4 : 2;
};
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
struct BitFields bitFields = {};
bitFields.bit1 = 0b0;
bitFields.bit2 = 0b1;
bitFields.bit3 = 0b1010;
bitFields.bit4 = 0b11;
union my_isa_t isa = {};
isa.bitfields = 0b00000011;
isa.bit1 = 0b0;
isa.bit2 = 0b1;
isa.bit3 = 0b1010;
isa.bit4 = 0b11;
union my_new_isa_t newIsa = {};
newIsa.bitfields = 0b00000011;
newIsa.bit1 = 0b0;
newIsa.bit2 = 0b1;
newIsa.bit3 = 0b1010;
newIsa.bit4 = 0b11;
NSLog(@"sizeof(bitfileds):%lu",sizeof(bitFields));
NSLog(@"bitFields.bit1:%d",bitFields.bit1);
NSLog(@"bitFields.bit2:%d",bitFields.bit2);
NSLog(@"bitFields.bit3:%d",bitFields.bit3);
NSLog(@"bitFields.bit4:%d",bitFields.bit4);
NSLog(@"sizeof(isa):%lu",sizeof(isa));
NSLog(@"isa.bit1:%d",isa.bit1);
NSLog(@"isa.bit2:%d",isa.bit2);
NSLog(@"isa.bit3:%d",isa.bit3);
NSLog(@"isa.bit4:%d",isa.bit4);
NSLog(@"isa.bitfileds:%d",isa.bitfields);
NSLog(@"sizeof(newIsa):%lu",sizeof(newIsa));
NSLog(@"newIsa.bit1:%d",newIsa.bit1);
NSLog(@"newIsa.bit2:%d",newIsa.bit2);
NSLog(@"newIsa.bit3:%d",newIsa.bit3);
NSLog(@"newIsa.bit4:%d",newIsa.bit4);
NSLog(@"newIsa.bitfields:%d",newIsa.bitfields);
NSLog(@"0b1111:%d",0b1111);
}
return 0;
}
-
log打印如下
??struct BitFileds 很多人都能理解刷喜,但是對(duì)union my_new_isa_t以及union my_isa_t 可能存在疑惑,我們一一進(jìn)行分析:
struct BitFileds
struct BitFileds {
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 4;
unsigned int bit4 : 2;
};
- 結(jié)構(gòu)體的每個(gè)成員都是獨(dú)立的立砸,struct BitFileds有4個(gè)位域類型的成員掖疮,一共占8位。其中bit1占第0位颗祝,bit2占第1位浊闪,bit3占第2~5位,bit4占第6~7位螺戳,結(jié)合內(nèi)存對(duì)齊可以知道struct BitFileds占4個(gè)字節(jié)搁宾。
-
struct BitFileds的內(nèi)存結(jié)構(gòu)圖如下
- 根據(jù)最后的整體內(nèi)存布局圖我們可以得到:
bit1=0b0=0
bit2=0b1=1
bit3=0b1010=10
bit4=0b11=3
union my_isa_t
union my_isa_t {
unsigned int bitfileds;
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 4;
unsigned int bit4 : 2;
};
該聯(lián)合體有5個(gè)成員,unsigned int類型的bitfields和4個(gè)位域bit1,bit2,bit3,bit4倔幼。
由于4個(gè)位域成員是屬于聯(lián)合體的盖腿,因此4個(gè)位域是參考聯(lián)合體進(jìn)行內(nèi)存布局,所以每個(gè)位域的開始位置都是第0位。
因此翩腐,bitfields占用4個(gè)字節(jié)32位(第1~32位)鸟款,bit1占用1個(gè)二進(jìn)制位(第0位),bit2占用1個(gè)二進(jìn)制位(第0位)茂卦,bit3占用4個(gè)二進(jìn)制位(第0~3位)何什,bit4占用2個(gè)二進(jìn)制位(第0~1位)。
-
union my_isa_t 的內(nèi)存結(jié)構(gòu)圖如下
結(jié)合最后的整體布局內(nèi)存可以得到:
bitfields=0b00001011=11
bit1=0b1=1
bit2=0b1=1
bit3=0b1011=11
bit4=0b11=3
union my_new_isa_t
union my_new_isa_t {
unsigned int bitfileds;
struct {
unsigned int bit1 : 1;
unsigned int bit2 : 1;
unsigned int bit3 : 4;
unsigned int bit4 : 2;
};
};
- 該聯(lián)合體有2個(gè)成員等龙,一個(gè)為unsigned int 類型的bitfileds处渣,一個(gè)為匿名結(jié)構(gòu)體,為了方便而咆,我們暫時(shí)把該匿名結(jié)構(gòu)體稱為s,s包含4個(gè)位域成員霍比。
- bitfileds大小為4字節(jié),s大小也是4個(gè)字節(jié)暴备,所以聯(lián)合體my_new_isa_t大小為4字節(jié)悠瞬,bitfileds和s共同占用4字節(jié)內(nèi)存空間。
- 4個(gè)位域成員是屬于結(jié)構(gòu)體的涯捻,所以位域成員的內(nèi)存布局是在匿名結(jié)構(gòu)體s下浅妆,參考結(jié)構(gòu)體s。由于結(jié)構(gòu)體的成員占用的內(nèi)存是互相獨(dú)立的障癌,因此bit1占用s的第1位凌外,bit2占用s的第2位,bit3占用s的第3~6位涛浙,bit4占用s的第7~8位康辑。
整體內(nèi)存結(jié)構(gòu)圖如下
- 結(jié)合最后的整體內(nèi)存布局圖可以得到:
bitfields=0b11101010=128+64+32+8+2=234
bit1=0b0=0
bit2=0b1=1
bit3=0b1010=10
bit4=0b11=3
結(jié)合demo中的三個(gè)例子大家應(yīng)該對(duì)位域有了深入的理解,對(duì)于位段成員類型不一致的情況轿亮,這里就不展開了疮薇,大家可以結(jié)合規(guī)則進(jìn)行內(nèi)存分析。
isa_t位域分析
- 結(jié)合上面的三個(gè)例子我注,我們自己也可以畫出isa_t的內(nèi)存結(jié)構(gòu)圖了
- isa_t根據(jù)cpu架構(gòu)bitfields也會(huì)有所不同
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
isa_t在arm64架構(gòu)下的內(nèi)存結(jié)構(gòu)圖
-
首先是匿名結(jié)構(gòu)體的內(nèi)存結(jié)構(gòu)圖
-
整體isa_t的內(nèi)存結(jié)構(gòu)圖
-
具體位域字段含義如下
-
nonpointer:表示是否對(duì) isa指針 開啟指針優(yōu)化按咒。
- 0:不優(yōu)化,是純isa指針但骨,當(dāng)訪問isa指針時(shí)励七,直接返回其成員變量cls。
- 1:優(yōu)化奔缠,即isa 指針內(nèi)容不止是類地址掠抬,還包含了類的一些信息、對(duì)象的引用計(jì)數(shù)等校哎。
- has_assoc:是否有關(guān)聯(lián)對(duì)象两波。
- has_cxx_dtor:該對(duì)象是否有C++或Objc的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做一些析構(gòu)的邏輯處理;如果沒有雨女,則可以更快的釋放對(duì)象。
- shiftcls:存儲(chǔ)類指針的值阳准。開啟指針優(yōu)化的情況下氛堕,在 x86_64 架構(gòu)有 44位 用來存儲(chǔ)類指針,arm64 架構(gòu)中有 33位 野蝇。
- magic:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象讼稚,還是一段沒有初始化的空間。
- weakly_referenced:用于標(biāo)識(shí)對(duì)象是否被指向或者曾經(jīng)被指向一個(gè)ARC的弱變量绕沈,沒有弱引用的對(duì)象釋放的更快锐想。
- deallocating:標(biāo)識(shí)對(duì)象是否正在釋放內(nèi)存。
- has_sidetable_rc:對(duì)象的引用計(jì)數(shù)值是否有進(jìn)位乍狐。
- extra_c:表示該對(duì)象的引用計(jì)數(shù)值赠摇。extra_rc只是存儲(chǔ)了額外的引用計(jì)數(shù),實(shí)際的引用計(jì)數(shù)公式:實(shí)際引用計(jì)數(shù) = extra_rc + 1浅蚪。
-
nonpointer:表示是否對(duì) isa指針 開啟指針優(yōu)化按咒。
通過位域成員藕帜,我們就能夠?qū)ξ挥虼淼亩M(jìn)制進(jìn)行方便的存取了,更重要的是大大節(jié)省了內(nèi)存空間的占用惜傲。
isa_t總結(jié)如下:isa_t聯(lián)合體有3個(gè)成員洽故,3個(gè)成員cls\bit\匿名結(jié)構(gòu)體s共同占用8字節(jié)的內(nèi)存空間,通過匿名結(jié)構(gòu)體里面的位域成員盗誊,可以對(duì)8字節(jié)空間的不同二進(jìn)制位進(jìn)行操作时甚,達(dá)到節(jié)省內(nèi)存空間的目的。
思考
- 如果我們把匿名結(jié)構(gòu)體中的位域換成基本數(shù)據(jù)類型來表示哈踱,結(jié)合內(nèi)存對(duì)齊原則荒适,得額外增加多少內(nèi)存空間的消耗?
我們可以算一下
// 在arm64下將位域換成基本數(shù)據(jù)類型
struct isa_t_notBitFields {
unsigned char nonpointer; // 1字節(jié)
unsigned char has_assoc; // 1字節(jié)
unsigned char has_cxx_dtor; // 1字節(jié)
unsigned long shiftcls; // 8字節(jié)
unsigned char magic; // 1字節(jié)
unsigned char weakly_referenced; // 1字節(jié)
unsigned char deallocating; // 1字節(jié)
unsigned char has_sidetable_rc; // 1字節(jié)
unsigned int extra_rc; // 4字節(jié)
};
- 答案是24個(gè)字節(jié)嚣鄙。你答對(duì)了么吻贿?
對(duì)結(jié)構(gòu)體所占空間大小、 內(nèi)存對(duì)齊這方面還存在疑惑的哑子,可以參考帶你深入理解iOS內(nèi)存對(duì)齊舅列。 - 通過位域,可以做每一個(gè)繼承自NSObject的對(duì)象都至少減少了16字節(jié)的內(nèi)存空間卧蜓,要知道1M內(nèi)存也就1024個(gè)字節(jié)帐要,這可是相當(dāng)可觀的!
總結(jié)
- 本文介紹了位域的概念和作用弥奸,結(jié)合實(shí)例讓讀者能夠掌握分析有關(guān)位域的內(nèi)存結(jié)構(gòu)圖榨惠,同時(shí)結(jié)合Apple的源碼isa_t的內(nèi)容,直觀感受位域的帶來的巨大好處,apple在優(yōu)化方面的可謂是盡可能做到了極致赠橙。