一、前置知識(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 ,bits ,struct,它們占用同一片內(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位)

| 成員 | 介紹 |
|---|---|
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))

打個(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);
可在 objc 中 runtime 里面的 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)證下

(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é):
- 先打印
p的內(nèi)存,取前8字節(jié),也就是isa。 - 打印
tp,是p實(shí)例的Class對(duì)象。 - 用
isa和ISA_MASK相與。得到值用p/x16進(jìn)制打印,驗(yàn)證和tp一樣。 -
p (Class)(0x001d800100003445 & 0x00007ffffffffff8ULL)強(qiáng)轉(zhuǎn)類型輸出也是LGPerson類。 - 對(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。 - 可驗(yàn)證上面所述。
參考
C語(yǔ)言中文版:https://wiki.jikexueyuan.com/project/c/c-bit-fields.html