iOS開(kāi)發(fā)之runtime(3):淺析NSObject對(duì)象的isa_t

logo

本系列博客是本人的源碼閱讀筆記副渴,如果有 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.bitsISA_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)了乓梨,歡迎大家下載。

壁紙寶貝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末清酥,一起剝皮案震驚了整個(gè)濱河市扶镀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌焰轻,老刑警劉巖臭觉,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異辱志,居然都是意外死亡蝠筑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)揩懒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)什乙,“玉大人,你說(shuō)我怎么就攤上這事已球〕剂停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵和悦,是天一觀(guān)的道長(zhǎng)退疫。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鸽素,這世上最難降的妖魔是什么褒繁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮馍忽,結(jié)果婚禮上棒坏,老公的妹妹穿的比我還像新娘燕差。我一直安慰自己,他們只是感情好坝冕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布徒探。 她就那樣靜靜地躺著,像睡著了一般喂窟。 火紅的嫁衣襯著肌膚如雪测暗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,021評(píng)論 1 291
  • 那天磨澡,我揣著相機(jī)與錄音碗啄,去河邊找鬼。 笑死稳摄,一個(gè)胖子當(dāng)著我的面吹牛稚字,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厦酬,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胆描,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了仗阅?” 一聲冷哼從身側(cè)響起昌讲,我...
    開(kāi)封第一講書(shū)人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎减噪,沒(méi)想到半個(gè)月后剧蚣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡旋廷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了礼搁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饶碘。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖馒吴,靈堂內(nèi)的尸體忽然破棺而出扎运,到底是詐尸還是另有隱情,我是刑警寧澤饮戳,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布豪治,位于F島的核電站,受9級(jí)特大地震影響扯罐,放射性物質(zhì)發(fā)生泄漏负拟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一歹河、第九天 我趴在偏房一處隱蔽的房頂上張望掩浙。 院中可真熱鬧花吟,春花似錦、人聲如沸厨姚。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谬墙。三九已至今布,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拭抬,已是汗流浹背部默。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玖喘,地道東北人甩牺。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像累奈,于是被迫代替她去往敵國(guó)和親贬派。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容