本系列博客是本人的源碼閱讀筆記副渴,如果有 iOS 開(kāi)發(fā)者在看 runtime 的,歡迎大家多多交流缚够。為了方便討論,本人新建了一個(gè)微信群(iOS技術(shù)討論群)抄瓦,想要加入的潮瓶,請(qǐng)?zhí)砑颖救宋⑿牛簔hujinhui207407,【加我前請(qǐng)備注:ios 】钙姊,本人博客http://www.kyson.cn 也在不停的更新中,歡迎一起討論
本文完整版詳見(jiàn)筆者小專(zhuān)欄:https://xiaozhuanlan.com/runtime
分析
上一篇文章中我們說(shuō)到isa其實(shí)是個(gè)聯(lián)合體埂伦,那什么是聯(lián)合體煞额,筆者再帶大家溫習(xí)一下:
聯(lián)合體
在進(jìn)行某些算法的C語(yǔ)言編程的時(shí)候,需要使幾種不同類(lèi)型的變量存放到同一段內(nèi)存單元中沾谜。也就是使用覆蓋技術(shù)膊毁,幾個(gè)變量互相覆蓋。這種幾個(gè)不同的變量共同占用一段內(nèi)存的結(jié)構(gòu)基跑,在C語(yǔ)言中婚温,被稱(chēng)作“共用體”類(lèi)型結(jié)構(gòu),簡(jiǎn)稱(chēng)共用體媳否,也叫聯(lián)合體栅螟。
聯(lián)合體和結(jié)構(gòu)體
“聯(lián)合”與“結(jié)構(gòu)”有一些相似之處。但兩者有本質(zhì)上的不同篱竭。在結(jié)構(gòu)中各成員有各自的內(nèi)存空間力图,一個(gè)結(jié)構(gòu)體變量的總長(zhǎng)度大于等于各成員長(zhǎng)度之和。而在“聯(lián)合”中掺逼,各成員共享一段內(nèi)存空間吃媒,一個(gè)聯(lián)合變量的長(zhǎng)度等于各成員中最長(zhǎng)的長(zhǎng)度。應(yīng)該說(shuō)明的是吕喘,這里所謂的共享不是指把多個(gè)成員同時(shí)裝入一個(gè)聯(lián)合變量?jī)?nèi)赘那,而是指該聯(lián)合變量可被賦予任一成員值,但每次只能賦一種值氯质,賦入新值則沖去舊值募舟。如下面介紹的“單位”變量,如定義為一個(gè)可裝入“班級(jí)”或“教研室”的聯(lián)合后病梢,就允許賦予整型值(班級(jí))或字符型(教研室)胃珍。要么賦予整型值梁肿,要么賦予字符型,不能把兩者同時(shí)賦予它觅彰。聯(lián)合類(lèi)型的定義和聯(lián)合變量的說(shuō)明:一個(gè)聯(lián)合類(lèi)型必須經(jīng)過(guò)定義之后吩蔑,才能把變量說(shuō)明為該聯(lián)合類(lèi)型。
演示代碼如下:
#include<iostream>
using namespace std;
union U1
{
int n;
char s[11];
double d;
};
union U2
{
int n;
char s[5];
double d;
};
int main()
{
U1 u1;
U2 u2;
cout<<sizeof(u1)<<'\t'<<sizeof(u2)<<endl;
cout<<"u1各數(shù)據(jù)地址:\n"<<&u1<<'\t'<<&u1.d<<'\t'<<&u1.s<<'\t'<<&u1.n<<endl;
cout<<"u1各數(shù)據(jù)地址:\n"<<&u2<<'\t'<<&u2.d<<'\t'<<&u2.s<<'\t'<<&u2.n<<endl;
}
上述代碼中:
對(duì)于U1聯(lián)合體填抬,s占11字節(jié)烛芬,n占4字節(jié),d占8字節(jié)飒责,因此其至少需1字節(jié)的空間赘娄。然而其實(shí)際大小并不是11,用運(yùn)算符sizeof測(cè)試其大小為16宏蛉。這是因?yàn)檫@里存在字節(jié)對(duì)齊的問(wèn)題遣臼,11既不能被4整除,也不能被8整除拾并。因此補(bǔ)充字節(jié)到16揍堰,這樣就符合所有成員的自身對(duì)齊了。從這里可以看出聯(lián)合體所占的空間不僅取決于最寬成員嗅义,還跟所有成員有關(guān)系屏歹,即其大小必須滿(mǎn)足兩個(gè)條件:
- (1)大小足夠容納最寬的成員;
- (2)大小能被其包含的所有基本數(shù)據(jù)類(lèi)型的大小所整除之碗。
對(duì)于U2聯(lián)合體蝙眶,同理知道,用運(yùn)算符sizeof測(cè)試其大小為8褪那。
運(yùn)行后的結(jié)果如下:
16 8
u1各數(shù)據(jù)地址:
0x7ffeefbff608 0x7ffeefbff608 0x7ffeefbff608 0x7ffeefbff608
u1各數(shù)據(jù)地址:
0x7ffeefbff5d0 0x7ffeefbff5d0 0x7ffeefbff5d0 0x7ffeefbff5d0
Program ended with exit code: 0
上篇文章中幽纷,我們比對(duì)兩個(gè)類(lèi)是否相等最終判斷了
isa.bits & ISA_MASK
的值是否相等。那為什么判斷這兩個(gè)的值是否相等即可呢武通,這即是本文討論的話(huà)題霹崎。
首先瀏覽一下isa源碼:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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
struct {
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
// SUPPORT_PACKED_ISA
#endif
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2
# define ISA_INDEX_IS_NPI 1
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15;
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;
# define RC_ONE (1ULL<<25)
# define RC_HALF (1ULL<<6)
};
# else
# error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif
};
去掉注釋?zhuān)约捌渌脚_(tái)的兼容性代碼(主要是x86_64相關(guān)的代碼)后簡(jiǎn)化如下:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
};
初步看到isa_t
的時(shí)候,相信大家還是比較難以理解:
- 結(jié)構(gòu)體后的冒號(hào)是什么意思
-
# define ISA_MASK 0x0000000ffffffff8ULL
定義的數(shù)字的含義
1.冒號(hào)是位域
位域
位域是指信息在存儲(chǔ)時(shí)冶忱,并不需要占用一個(gè)完整的字節(jié)尾菇, 而只需占幾個(gè)或一個(gè)二進(jìn)制位。例如在存放一個(gè)開(kāi)關(guān)量時(shí)囚枪,只有0和1 兩種狀態(tài)派诬, 用一位二進(jìn)位即可。為了節(jié)省存儲(chǔ)空間链沼,并使處理簡(jiǎn)便默赂,C語(yǔ)言又提供了一種數(shù)據(jù)結(jié)構(gòu),稱(chēng)為“位域”或“位段”括勺。所謂“位域”是把一個(gè)字節(jié)中的二進(jìn)位劃分為幾 個(gè)不同的區(qū)域缆八, 并說(shuō)明每個(gè)區(qū)域的位數(shù)曲掰。每個(gè)域有一個(gè)域名,允許在程序中按域名進(jìn)行操作奈辰。 這樣就可以把幾個(gè)不同的對(duì)象用一個(gè)字節(jié)的二進(jìn)制位域來(lái)表示栏妖。
首先大家需要知道,不管X86還是arm的處理器都是64位的奖恰。16位操作系統(tǒng)中吊趾,int 占16位;在32位操作系統(tǒng)中瑟啃,int 占32位论泛。但是后來(lái)人們已經(jīng)習(xí)慣了 int 占32位,因此在64位操作系統(tǒng)中蛹屿,int 仍為32位屁奏。64位整型用 long long 或者64位即8個(gè)字節(jié),即64位错负。
在文章結(jié)構(gòu)體對(duì)齊(圖解)與位域 中大家可以了解到位域?qū)τ诮Y(jié)構(gòu)體的大小起到一定的作用了袁。
因此我們不難理解:isa_t中的bits占用了64位的數(shù)據(jù)。
上一篇文章中的
isa.bits & ISA_MASK
中
# define ISA_MASK 0x0000000ffffffff8ULL
我們來(lái)看一下湿颅,這個(gè)0x0000000ffffffff8ULL
換算成二進(jìn)制
本文完整版詳見(jiàn)筆者小專(zhuān)欄:https://xiaozhuanlan.com/runtime
有31位都是1。 對(duì)isa.bits
和ISA_MASK
進(jìn)行與操作粥诫,會(huì)發(fā)生什么“化學(xué)反應(yīng)”呢油航?
位操作符
位操作符包括:&(按位與)、|(按位或)怀浆、^(按位異或)谊囚。這三個(gè)操作符非常簡(jiǎn)單,需要注意的是执赡,這三個(gè)操作符操作的必須是整數(shù)镰踏。
這里以&為例:
當(dāng)&兩邊是bool類(lèi)型的值時(shí),該運(yùn)算符作為邏輯運(yùn)算符截驮。作用如下:
當(dāng)運(yùn)算符兩邊的表達(dá)式的結(jié)果都為true時(shí)缰泡,整個(gè)運(yùn)算結(jié)果才為true铝宵,否則,只要有一方為false绊率,則結(jié)果為false。
當(dāng)&兩邊不是bool類(lèi)型的時(shí)候究履,該運(yùn)算符作為位運(yùn)算符滤否,將兩邊的值作為二進(jìn)制展開(kāi),依次對(duì)每一位進(jìn)行 按位與最仑。作用如下:
11100101 & 01011010 = 01000000
經(jīng)過(guò)以上分析藐俺,我們不難得出:
上圖中可以看出ISA_MASK的值轉(zhuǎn)化為二進(jìn)制中有33位都為1炊甲,上面的例子可以看出,按位與的作用是可以取出這33位中的值欲芹。那么此時(shí)很明顯了卿啡,同ISA_MASK進(jìn)行按位與運(yùn)算即可以取出Class的值。
寫(xiě)到這里耀石,我們?cè)倩仡^看看isa_t的源碼牵囤,不難發(fā)現(xiàn),這33位對(duì)應(yīng)的是結(jié)構(gòu)體的shiftcls
的位域滞伟。其他的位域在最后也一并做個(gè)預(yù)習(xí)吧:
struct {
// 1代表優(yōu)化后的使用位域存儲(chǔ)更多的信息揭鳞。
uintptr_t nonpointer : 1;
// 是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象
uintptr_t has_assoc : 1;
// 是否有C++析構(gòu)函數(shù)
uintptr_t has_cxx_dtor : 1;
// 存儲(chǔ)著Class對(duì)象的內(nèi)存地址信息
uintptr_t shiftcls : 33;
// 用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
uintptr_t magic : 6;
// 是否有被弱引用指向過(guò)。
uintptr_t weakly_referenced : 1;
// 對(duì)象是否正在釋放
uintptr_t deallocating : 1;
// 引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中
// 如果為1梆奈,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類(lèi)的屬性中
uintptr_t has_sidetable_rc : 1;
// 里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
uintptr_t extra_rc : 19;
};
總結(jié):
本文從isa的bits出發(fā)野崇,總結(jié)了NSObject對(duì)象的聯(lián)合體isa
的部分字段的含義。希望大家對(duì)isa有更深的理解亩钟。
本文完整版詳見(jiàn)筆者小專(zhuān)欄:https://xiaozhuanlan.com/runtime
廣告
我的首款個(gè)人開(kāi)發(fā)的APP壁紙寶貝上線(xiàn)了乓梨,歡迎大家下載。