一陨献、前置知識(shí)
1.1 C 共用體 || 聯(lián)合體
共用體是一種特殊的數(shù)據(jù)類(lèi)型,允許您在相同的內(nèi)存位置存儲(chǔ)不同的數(shù)據(jù)類(lèi)型纽匙。您可以定義一個(gè)帶有多成員的共用體厌丑,但是任何時(shí)候只能有一個(gè)成員帶有值。共用體提供了一種使用相同的內(nèi)存位置的有效方式匹耕。
定義
為了定義結(jié)構(gòu)體聚请,您必須使用 union
語(yǔ)句,方式與定義結(jié)構(gòu)類(lèi)似。union
語(yǔ)句定義了一個(gè)新的數(shù)據(jù)類(lèi)型驶赏,帶有多個(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
類(lèi)型的變量可以存儲(chǔ)一個(gè)整數(shù)煤傍、一個(gè)浮點(diǎn)數(shù)盖文,或者一個(gè)字符串。這意味著一個(gè)變量(相同的內(nèi)存位置)可以存儲(chǔ)多個(gè)多種類(lèi)型的數(shù)據(jù)蚯姆。您可以根據(jù)需要在一個(gè)共用體內(nèi)使用任何內(nèi)置的或者用戶(hù)自定義的數(shù)據(jù)類(lèi)型五续。
共用體占用的內(nèi)存應(yīng)足夠存儲(chǔ)共用體中最大的成員。例如龄恋,在上面的實(shí)例中疙驾,Data
將占用 8 個(gè)字節(jié)的內(nèi)存空間,因?yàn)樵诟鱾€(gè)成員中郭毕,height
所占用的空間是最大的它碎。
訪(fǎng)問(wèn)共用體成員
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)證最大空間為公用體中,最大的成員所占空間铣卡。也可知成員變量是訪(fǎng)問(wèn)的同一片內(nèi)存空間链韭。
1.2 C 位域
如果程序的結(jié)構(gòu)中包含多個(gè)開(kāi)關(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)可以重寫(xiě)成:
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ù)類(lèi)型亥揖,決定了如何解釋位域的值珊擂。類(lèi)型可以是整型、有符號(hào)整型、無(wú)符號(hào)整型摧扇。 |
member_name | 位域的名稱(chēng)圣贸。 |
width | 位域中位的數(shù)量。寬度必須小于或等于指定類(lèi)型的位寬度扳剿。 |
帶有預(yù)定義寬度的變量被稱(chēng)為位域旁趟。位域可以存儲(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è)值,如果您試圖使用超過(guò) 3 位凡傅,則無(wú)法完成辟狈。
二、isa探索
在 探索alloc 的時(shí)候夏跷,最后有個(gè)方法 initInstanceIsa
初始化 isa
并關(guān)聯(lián)類(lèi)哼转。在此繼續(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__
: 是代表手表的宏,不滿(mǎn)足
__arm64__
: 表示64位ARM架構(gòu) 滿(mǎn)足
__LP64__
: 表示指針長(zhǎng)度為64位 滿(mǎn)足
!__LP64__
: 就不滿(mǎn)足了
所以 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
};
通過(guò)源碼可知壹蔓,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 指針開(kāi)啟指針優(yōu)化——0:純 isa 指針灌砖;1:不止是類(lèi)對(duì)象地址璧函,isa 中包含了類(lèi)信息、對(duì)象的引用計(jì)數(shù)等 |
has_assoc |
關(guān)聯(lián)對(duì)象標(biāo)志位周崭,0沒(méi)有柳譬,1存在 |
has_cxx_dtor |
該對(duì)象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯续镇, 如果沒(méi)有美澳,則可以更快的釋放對(duì)象 |
shiftcls |
存儲(chǔ)類(lèi)指針的值,在開(kāi)啟指針優(yōu)化的情況下,在 arm64 架構(gòu)中有 33 位用來(lái)存儲(chǔ)類(lèi)指針 |
magic |
用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間 |
weakly_referenced |
對(duì)象是否被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量制跟, 沒(méi)有弱引用的對(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
類(lèi)型的,查看定義
typedef unsigned long uintptr_t;
根據(jù)內(nèi)存對(duì)齊原則情臭,可知 Class
肯定是 8 字節(jié)對(duì)齊的省撑,同樣的,cls
的指向地址(也既開(kāi)始地址)肯定是 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)合體的說(shuō)明肥败,共用內(nèi)存,可見(jiàn)蘋(píng)果設(shè)計(jì)優(yōu)化節(jié)省內(nèi)存的良苦用心愕提。
賦值 shiftcls
的時(shí)候既沒(méi)有改變 cls
的值馒稍,也最大的優(yōu)化了內(nèi)存使用。
我先開(kāi)始到分析到這的時(shí)候浅侨,還是有疑問(wèn)纽谒,存的時(shí)候是這樣,但是取的時(shí)候呢如输?取的時(shí)候前三位不是 000
啊鼓黔,對(duì)央勒,取的時(shí)候前三位確實(shí)不是 000
,包括后面的幾位澳化。但是蘋(píng)果在取的時(shí)候崔步,又做了操作,接著往下分析
那賦值了 shiftcls
是不是就證明了我們關(guān)聯(lián)了類(lèi)呢缎谷?通常通過(guò) 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());
// 上面分析過(guò) 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/x
16進(jìn)制打印甥厦,驗(yàn)證和tp
一樣纺铭。 -
p (Class)(0x001d800100003445 & 0x00007ffffffffff8ULL)
強(qiáng)轉(zhuǎn)類(lèi)型輸出也是LGPerson類(lèi)。 - 對(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