iOS底層探索之isa

一、前置知識(shí)

1.1 C 共用體 || 聯(lián)合體

共用體是一種特殊的數(shù)據(jù)類型,允許您在相同的內(nèi)存位置存儲(chǔ)不同的數(shù)據(jù)類型。您可以定義一個(gè)帶有多成員的共用體,但是任何時(shí)候只能有一個(gè)成員帶有值。共用體提供了一種使用相同的內(nèi)存位置的有效方式。

定義
為了定義結(jié)構(gòu)體,您必須使用 union 語(yǔ)句,方式與定義結(jié)構(gòu)類似。union 語(yǔ)句定義了一個(gè)新的數(shù)據(jù)類型,帶有多個(gè)成員。union 語(yǔ)句的格式如下:

 union [union tag]
    {
       member definition;
       member definition;
       ...
       member definition;
    } [one or more union variables];
// [one or more union variables] 是可選的
--
eg:
  union uData {
    int age;        // 4字節(jié)
    long height;    // 8字節(jié)
    char sex;       // 1字節(jié)
  };

現(xiàn)在,Data 類型的變量可以存儲(chǔ)一個(gè)整數(shù)、一個(gè)浮點(diǎn)數(shù),或者一個(gè)字符串。這意味著一個(gè)變量(相同的內(nèi)存位置)可以存儲(chǔ)多個(gè)多種類型的數(shù)據(jù)。您可以根據(jù)需要在一個(gè)共用體內(nèi)使用任何內(nèi)置的或者用戶自定義的數(shù)據(jù)類型。

共用體占用的內(nèi)存應(yīng)足夠存儲(chǔ)共用體中最大的成員。例如,在上面的實(shí)例中,Data 將占用 8 個(gè)字節(jié)的內(nèi)存空間,因?yàn)樵诟鱾€(gè)成員中,height 所占用的空間是最大的。

訪問共用體成員

        union uData u = {};
        u.height = 100;
        NSLog(@"u: %lu", sizeof(u));
        NSLog(@"age:%d, height: %ld, age: %hhd", u.age, u.height, u.sex);
        u.age = 25;
        NSLog(@"age:%d, height: %ld, age: %hhd", u.age, u.height, u.sex);
        u.sex = 'f';
        NSLog(@"age:%d, height: %ld, age: %hhd", u.age, u.height, u.sex); 

---
age:100, height: 100, age: 100
age:25, height: 25, age: 25
age:102, height: 102, age: 102

可驗(yàn)證最大空間為公用體中,最大的成員所占空間。也可知成員變量是訪問的同一片內(nèi)存空間。

1.2 C 位域

如果程序的結(jié)構(gòu)中包含多個(gè)開關(guān)量,只有 TRUE/FALSE 變量,如下:

    struct
    {
      unsigned int widthValidated;
      unsigned int heightValidated;
    } status;

這種結(jié)構(gòu)需要 8 字節(jié)的內(nèi)存空間,但在實(shí)際上,在每個(gè)變量中,我們只存儲(chǔ) 0 或 1。在這種情況下,C 語(yǔ)言提供了一中更好的利用內(nèi)存空間的方式。如果您在結(jié)構(gòu)內(nèi)使用這樣的變量,您可以定義變量的寬度來(lái)告訴編譯器,您將只使用這些字節(jié)。例如,上面的結(jié)構(gòu)可以重寫成:

   struct
    {
      unsigned int widthValidated : 1;
      unsigned int heightValidated : 1;
    } status;

現(xiàn)在,上面的結(jié)構(gòu)中,status 變量將占用 4 個(gè)字節(jié)的內(nèi)存空間,但是只有 2 位被用來(lái)存儲(chǔ)值。如果您用了 32 個(gè)變量,每一個(gè)變量寬度為 1 位,那么 status 結(jié)構(gòu)將使用 4 個(gè)字節(jié)。

位域聲明

    struct
    {
      type [member_name] : width ;
    };

下面是有關(guān)位域中變量元素的描述:

元素 描述
type 整數(shù)類型,決定了如何解釋位域的值。類型可以是整型、有符號(hào)整型、無(wú)符號(hào)整型。
member_name 位域的名稱。
width 位域中位的數(shù)量。寬度必須小于或等于指定類型的位寬度。

帶有預(yù)定義寬度的變量被稱為位域。位域可以存儲(chǔ)多于 1 位的數(shù),例如,需要一個(gè)變量來(lái)存儲(chǔ)從 0 到 7 的值,您可以定義一個(gè)寬度為 3 位的位域,如下:

 struct
    {
      unsigned int age : 3;
    } Age;

上面的結(jié)構(gòu)定義指示 C 編譯器,age 變量將只使用 3 位來(lái)存儲(chǔ)這個(gè)值,如果您試圖使用超過 3 位,則無(wú)法完成。


二、isa探索

探索alloc 的時(shí)候,最后有個(gè)方法 initInstanceIsa 初始化 isa 并關(guān)聯(lián)類。在此繼續(xù)深入

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);
 
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

查看下 SUPPORT_INDEXED_ISA 的定義

#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

__ARM_ARCH_7K__: 是代表手表的宏,不滿足
__arm64__: 表示64位ARM架構(gòu) 滿足
__LP64__: 表示指針長(zhǎng)度為64位 滿足
!__LP64__: 就不滿足了

所以 SUPPORT_INDEXED_ISA 為 0

由此分析上面的主要代碼為

        isa_t newisa(0);  // 聲明并初始化一個(gè)isa_t
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3; // 重點(diǎn),賦值了cls
        isa = newisa; // 賦值isa
2.1 isa_t結(jié)構(gòu)
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
};

通過源碼可知,isa_t 是一個(gè)聯(lián)合體,里面有3個(gè)成員 cls ,bitsstruct,它們占用同一片內(nèi)存區(qū)域。

然后找到 ISA_BITFIELD 的定義

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

運(yùn)行環(huán)境是在mac上運(yùn)行的,不是跑的真機(jī),可以以 __x86_64__ 為例探索一下。

綜上所述,所以isa_t的聯(lián)合體,其中 shiftcls

  • arm64 下占33位(從第3位到35位)
  • __x86_64__ 下占44位(從第3位到46位)
union-isa_t存儲(chǔ).png
成員 介紹
nonpointer 表示是否對(duì) isa 指針開啟指針優(yōu)化——0:純 isa 指針;1:不止是類對(duì)象地址,isa 中包含了類信息、對(duì)象的引用計(jì)數(shù)等
has_assoc 關(guān)聯(lián)對(duì)象標(biāo)志位,0沒有,1存在
has_cxx_dtor 該對(duì)象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯, 如果沒有,則可以更快的釋放對(duì)象
shiftcls 存儲(chǔ)類指針的值,在開啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位用來(lái)存儲(chǔ)類指針
magic 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間
weakly_referenced 對(duì)象是否被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量, 沒有弱引用的對(duì)象可以更快釋放
deallocating 標(biāo)志對(duì)象是否正在釋放內(nèi)存
has_sidetable_rc 當(dāng)對(duì)象引用技術(shù)大于 10 時(shí),則需要借用該變量存儲(chǔ)進(jìn)位
extra_rc 當(dāng)表示該對(duì)象的引用計(jì)數(shù)值,實(shí)際上是引用計(jì)數(shù)值減 1, 例如,如果對(duì)象的引用計(jì)數(shù)為 10,那么 extra_rc 為 9。如果引用計(jì)數(shù)大于 10, 則需要使用到下面的 has_sidetable_rc
2.2 shiftcls賦值 & 為什么右移3位(重點(diǎn))
newisa打印.png

打個(gè)斷點(diǎn),在將要賦值 shiftcls 的時(shí)候,現(xiàn)在里面的所有值,都是因?yàn)?newisa.bits = ISA_MAGIC_VALUE 而來(lái)。(原因參考前置知識(shí)聯(lián)合體)

好的,重點(diǎn)來(lái)了,為什么賦值 shiftcls 的時(shí)候要右移3位?

cls 是一個(gè) Class 對(duì)象,注意是對(duì)象,點(diǎn)進(jìn)去看一下定義

typedef struct objc_class *Class;
---
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    // 省略 ...
}

再繼續(xù)看 cache_t 、class_data_bits_t 發(fā)現(xiàn)都是結(jié)構(gòu)體,然后看里面的成員變量,大部分都是 uintptr_t 類型的,查看定義

typedef unsigned long           uintptr_t;

根據(jù)內(nèi)存對(duì)齊原則,可知 Class 肯定是 8 字節(jié)對(duì)齊的,同樣的,cls的指向地址(也既開始地址)肯定是 8 的倍數(shù), 轉(zhuǎn)換成二進(jìn)制后,低三位肯定是 000。

繼續(xù)上圖的打印:

(lldb) p (uintptr_t)cls
(uintptr_t) $1 = 4294980728
(lldb) p/t (uintptr_t)cls
(uintptr_t) $2 = 0b0000000000000000000000000000000100000000000000000011010001111000
(lldb) 

再聯(lián)想聯(lián)合體的說明,共用內(nèi)存,可見蘋果設(shè)計(jì)優(yōu)化節(jié)省內(nèi)存的良苦用心。
賦值 shiftcls 的時(shí)候既沒有改變 cls 的值,也最大的優(yōu)化了內(nèi)存使用。

我先開始到分析到這的時(shí)候,還是有疑問,存的時(shí)候是這樣,但是取的時(shí)候呢?取的時(shí)候前三位不是 000 啊,對(duì),取的時(shí)候前三位確實(shí)不是 000,包括后面的幾位。但是蘋果在取的時(shí)候,又做了操作,接著往下分析

那賦值了 shiftcls 是不是就證明了我們關(guān)聯(lián)了類呢?通常通過 x/4gx 打印實(shí)例的時(shí)候,第一個(gè)輸出的 8 字節(jié)到底是不是 isa 呢?

2.3 object_getClass 驗(yàn)證isa是否存的是class
        LGPerson *p = [LGPerson alloc];
        // #import <malloc/malloc.h>
        // Returns the class of an object.
        id tp = object_getClass(p); 

可在 objcruntime 里面的 objc-class.mm 源碼中查到

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

objc_object

inline Class 
objc_object::getIsa() 
{    // oc對(duì)象可認(rèn)為isTaggedPointer為false
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

會(huì)調(diào)用 ISA()

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
  // 上面分析過 SUPPORT_INDEXED_ISA = 0
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

最終返回的是 (Class)(isa.bits & ISA_MASK)。
__x86_64__# define ISA_MASK 0x00007ffffffffff8ULL

知道計(jì)算規(guī)則后,咱們驗(yàn)證下

驗(yàn)證isa指向class.png
(lldb) x/4gx p
0x10075eab0: 0x001d800100003445 0x0000000000000000
0x10075eac0: 0x0000000000000000 0x0000000000000000
(lldb) p tp
(id) $1 = 0x0000000100003440
(lldb) p 0x001d800100003445 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 4294980672
(lldb) p (Class)(0x001d800100003445 & 0x00007ffffffffff8ULL)
(Class) $3 = LGPerson
(lldb) p/x 4294980672
(long) $4 = 0x0000000100003440

總結(jié)

  1. 先打印p的內(nèi)存,取前8字節(jié),也就是 isa。
  2. 打印 tp,是p實(shí)例的Class對(duì)象。
  3. isaISA_MASK 相與。得到值用 p/x 16進(jìn)制打印,驗(yàn)證和 tp 一樣。
  4. p (Class)(0x001d800100003445 & 0x00007ffffffffff8ULL) 強(qiáng)轉(zhuǎn)類型輸出也是LGPerson類。
  5. 對(duì)應(yīng) newisa.shiftcls 賦值時(shí)右移三位,0x00007ffffffffff8 (__x86_64__) 轉(zhuǎn)成2進(jìn)制,可發(fā)現(xiàn) 0~2,47~63 位都是0,中間 3~46 的44位為1,所以取的就是 shiftcls
  6. 可驗(yàn)證上面所述。

參考

C語(yǔ)言中文版:https://wiki.jikexueyuan.com/project/c/c-bit-fields.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容