我們要進(jìn)行isa的分析首先掌握的知識(shí)
1售貌、聯(lián)合體(共用體)
1.1、使用位運(yùn)算 進(jìn)行 存取 數(shù)據(jù)
1.2啃憎、位域 簡(jiǎn)介
1.3、結(jié)構(gòu)體位域優(yōu)化代碼
1.4似炎、聯(lián)合體優(yōu)化代碼
2辛萍、clang
2.1 clang 簡(jiǎn)介
2.2 簡(jiǎn)單用法
2.3 clang 源碼 查看線索
3悯姊、isa_t聯(lián)合體
1、聯(lián)合體union
聯(lián)合體union
的定義方式與結(jié)構(gòu)體一樣贩毕,但是二者有根本區(qū)別悯许。
什么是聯(lián)合體(union)呢?聯(lián)合體是一種特殊的類辉阶,也是一種構(gòu)造類型的數(shù)據(jù)結(jié)構(gòu)先壕。完全就是共用一個(gè)內(nèi)存首地址,并且各種變量名都可以同時(shí)使用睛藻,操作也是共同生效启上。所以也叫共用體。并且聯(lián)合體(union)中是各變量是“互斥”的店印,但是內(nèi)存使用更為精細(xì)靈活,也節(jié)省了內(nèi)存空間倒慧。
在結(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)度
。
1.1付秕、使用位運(yùn)算 進(jìn)行 存取 數(shù)據(jù)
廢話不多 我們定義一個(gè)LHCar
類 這個(gè)類 有up
down
left
right
四個(gè)代表方向的BOOL類型
的屬性
@interface LHCar : NSObject
@property (nonatomic, assign) BOOL up;
@property (nonatomic, assign) BOOL down;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@end
進(jìn)行打印
2020-09-09 23:41:04.086760+0800 isa結(jié)構(gòu)分析[4982:341540] 16
輸出為16字節(jié) 其中包括 isa
和4個(gè)BOOL類型的屬性 共 8
+1
+1
+1
+1
= 12
內(nèi)存對(duì)齊 為 16 字節(jié)
我們知道 BOOL
值 只有兩種情況 0
或1
, 一個(gè)字節(jié)
有8個(gè)二進(jìn)制位
兰珍,并且二進(jìn)制 只有 0
或1
想到這里 那么我們完全可以使用一個(gè)二進(jìn)制位來表示 一個(gè)BOOL
值 。也就是這四個(gè)BOOL
屬性 我們可以用4個(gè)二進(jìn)制位
來表示如下圖 询吴,大大節(jié)省了內(nèi)存空間
按照我們的臆想來實(shí)現(xiàn)代碼掠河,首先分別聲明 up down left right 掩碼 mask ,來方便我們位運(yùn)算取值賦值
#define LHDirectionUpMask 0b00001000
#define LHDirectionDownMask 0b00000100
#define LHDirectionLeftMask 0b00000010
#define LHDirectionRightMask 0b00000001
定義 char 類型 的成員變量
@interface LHCar(){
char _upDownLeftRight;
}
@end
初始化
- (instancetype)init
{
self = [super init];
if (self) {
_upDownLeftRight = 0b00000001;
}
return self;
}
自定義setter
-(void)setUp:(BOOL)up
{
if (up) {
/// 如果需要將值置為1猛计,將源碼和掩碼進(jìn)行按位或運(yùn)算
_upDownLeftRight |= LHDirectionUpMask;
}else{
/// 如果需要將值置為0 // 將源碼和按位取反后的掩碼進(jìn)行按位與運(yùn)算
_upDownLeftRight &= ~LHDirectionUpMask;
}
}
-(void)setDown:(BOOL)down
{
if (down) {
_upDownLeftRight |= LHDirectionDownMask;
}else{
_upDownLeftRight &= ~ LHDirectionDownMask;
}
}
- (void)setLeft:(BOOL)left
{
if (left) {
_upDownLeftRight |= LHDirectionLeftMask;
} else {
_upDownLeftRight &= ~LHDirectionLeftMask;
}
}
- (void)setRight:(BOOL)right
{
if (right) {
_upDownLeftRight |= LHDirectionRightMask;
} else {
_upDownLeftRight &= ~LHDirectionRightMask;
}
}
getter
-(BOOL)isUp
{
return !!(_upDownLeftRight & LHDirectionUpMask);
}
-(BOOL)isDown
{
return !!(_upDownLeftRight & LHDirectionDownMask);
}
-(BOOL)isLeft
{
return !!(_upDownLeftRight & LHDirectionLeftMask);
}
-(BOOL)isRight
{
return !!(_upDownLeftRight & LHDirectionRightMask);
}
按照?qǐng)D上示意 及我們的想法 這時(shí)候調(diào)用getter方法打印 初始化值
LHCar * car = [[LHCar alloc]init];
NSLog(@"up:%d down:%d left:%d right:%d",car.isUp,car.isDown,car.isLeft,car.isRight);
我們屏蔽之前定義的屬性 調(diào)用自定義的setter方法進(jìn)行賦值
LHCar * car = [[LHCar alloc]init];
[car setUp:YES];
[car setDown:NO];
[car setLeft:YES];
[car setRight:NO];
NSLog(@"up:%d down:%d left:%d right:%d",car.isUp,car.isDown,car.isLeft,car.isRight);
發(fā)現(xiàn)我們用一個(gè)字節(jié)的里的4個(gè)二進(jìn)制位 就完成了 之前的 占有4個(gè)字節(jié)的 4個(gè)屬性的讀寫
1.2唠摹、位域 簡(jiǎn)介
有些信息在存儲(chǔ)時(shí),并不需要占用一個(gè)完整的字節(jié)奉瘤,而只需占幾個(gè)或一個(gè)二進(jìn)制位勾拉。例如在存放一個(gè)開關(guān)量時(shí),只有0
和1
兩種狀態(tài)盗温,用1位
二進(jìn)制位
即可藕赞。為了節(jié)省存儲(chǔ)空間并使處理簡(jiǎn)便,C語言又提供了一種數(shù)據(jù)結(jié)構(gòu)卖局,稱為"位域"或"位段"
所謂"位域"是把一個(gè)字節(jié)中的二進(jìn)位劃分為幾個(gè)不同的區(qū)域斧蜕,并說明每個(gè)區(qū)域的位數(shù)。每個(gè)域有一個(gè)域名吼驶,允許在程序中按域名進(jìn)行操作惩激。這樣就可以把幾個(gè)不同的對(duì)象用一個(gè)字節(jié)的二進(jìn)制位域來表示店煞。
位域的定義和位域變量的說明
struct 位域結(jié)構(gòu)名
{
位域列表
};
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
說明 data 為 bs 變量,共占兩個(gè)字節(jié)风钻,其中位域a占8位顷蟀,位域b占2位,位域 c 占6位骡技。
位域定義說明
1鸣个、 一個(gè)位域必須存儲(chǔ)在同一個(gè)字節(jié)中,不能跨兩個(gè)字節(jié)布朦。如一個(gè)字節(jié)所识谟空間不夠存放另一位域時(shí),應(yīng)從下一單元起存放該位域
2是趴、由于位域不允許跨兩個(gè)字節(jié)涛舍,因此位域的長(zhǎng)度不能大于一個(gè)字節(jié)的長(zhǎng)度,也就是說不能超過8位二進(jìn)位唆途。(如果最大長(zhǎng)度大于計(jì)算機(jī)的整數(shù)字長(zhǎng)富雅,一些編譯器可能會(huì)允許域的內(nèi)存重疊,另外一些編譯器可能會(huì)把大于一個(gè)域的部分存儲(chǔ)在下一個(gè)字中肛搬。)
3没佑、位域可以是無名位域,這時(shí)它只用來作填充或調(diào)整位置温赔。無名的位域是不能使用的
1.3蛤奢、結(jié)構(gòu)體位域優(yōu)化代碼
我們了解了位域的基本信息那么我們可以用結(jié)構(gòu)體位域來優(yōu)化代碼
#import "LHCar.h"
//#define LHDirectionUpMask 0b00001000
//#define LHDirectionDownMask 0b00000100
//#define LHDirectionLeftMask 0b00000010
//#define LHDirectionRightMask 0b00000001
@interface LHCar(){
//
// char _upDownLeftRight;
struct{
char up : 1;
char down : 1;
char left : 1;
char right: 1;
}_upDownLeftRight;
}
@end
@implementation LHCar
- (instancetype)init
{
self = [super init];
if (self) {
// _upDownLeftRight = 0b00000001;
}
return self;
}
-(void)setUp:(BOOL)up
{
// if (up) {
// /// 如果需要將值置為1,將源碼和掩碼進(jìn)行按位或運(yùn)算
// _upDownLeftRight |= LHDirectionUpMask;
// }else{
// /// 如果需要將值置為0 // 將源碼和按位取反后的掩碼進(jìn)行按位與運(yùn)算
// _upDownLeftRight &= ~LHDirectionUpMask;
// }
_upDownLeftRight.up = up;
}
-(void)setDown:(BOOL)down
{
// if (down) {
// _upDownLeftRight |= LHDirectionDownMask;
// }else{
// _upDownLeftRight &= ~ LHDirectionDownMask;
// }
_upDownLeftRight.down = down;
}
- (void)setLeft:(BOOL)left
{
// if (left) {
// _upDownLeftRight |= LHDirectionLeftMask;
// } else {
// _upDownLeftRight &= ~LHDirectionLeftMask;
// }
_upDownLeftRight.left = left;
}
- (void)setRight:(BOOL)right
{
// if (right) {
// _upDownLeftRight |= LHDirectionRightMask;
// } else {
// _upDownLeftRight &= ~LHDirectionRightMask;
// }
_upDownLeftRight.right = right;
}
-(BOOL)isUp
{
// return !!(_upDownLeftRight & LHDirectionUpMask);
return !!_upDownLeftRight.up;
}
-(BOOL)isDown
{
// return !!(_upDownLeftRight & LHDirectionDownMask);
return !!_upDownLeftRight.down;
}
-(BOOL)isLeft
{
// return !!(_upDownLeftRight & LHDirectionLeftMask);
return !!_upDownLeftRight.left;
}
-(BOOL)isRight
{
// return !!(_upDownLeftRight & LHDirectionRightMask);
return !!_upDownLeftRight.right;
}
@end
這樣是可以正常存取 但是去掉了 掩碼mask 和初始化代碼 導(dǎo)致可讀性非常差 這時(shí)聯(lián)合體出來了
1.4陶贼、聯(lián)合體優(yōu)化代碼
#import "LHCar.h"
#define LHDirectionUpMask 0b00001000
#define LHDirectionDownMask 0b00000100
#define LHDirectionLeftMask 0b00000010
#define LHDirectionRightMask 0b00000001
@interface LHCar(){
union{
char bits;
struct{
char up : 1;
char down : 1;
char left : 1;
char right: 1;
};
}_upDownLeftRight;
}
@end
@implementation LHCar
- (instancetype)init
{
self = [super init];
if (self) {
_upDownLeftRight.bits = 0b00000001;
}
return self;
}
-(void)setUp:(BOOL)up
{
if (up) {
_upDownLeftRight.bits |= LHDirectionUpMask;
}else{
_upDownLeftRight.bits &= ~LHDirectionUpMask;
}
}
-(void)setDown:(BOOL)down
{
if (down) {
_upDownLeftRight.bits |= LHDirectionDownMask;
}else{
_upDownLeftRight.bits &= ~ LHDirectionDownMask;
}
}
- (void)setLeft:(BOOL)left
{
if (left) {
_upDownLeftRight.bits |= LHDirectionLeftMask;
} else {
_upDownLeftRight.bits &= ~LHDirectionLeftMask;
}
}
- (void)setRight:(BOOL)right
{
if (right) {
_upDownLeftRight.bits |= LHDirectionRightMask;
} else {
_upDownLeftRight.bits &= ~LHDirectionRightMask;
}
}
-(BOOL)isUp
{
return !!(_upDownLeftRight.bits & LHDirectionUpMask);
}
-(BOOL)isDown
{
return !!(_upDownLeftRight.bits & LHDirectionDownMask);
}
-(BOOL)isLeft
{
return !!(_upDownLeftRight.bits & LHDirectionLeftMask);
}
-(BOOL)isRight
{
return !!(_upDownLeftRight.bits & LHDirectionRightMask);
}
@end
測(cè)試發(fā)現(xiàn)依舊可以 完成 存取
其中 _upDownLeftRight
聯(lián)合體只 占用了一個(gè)字節(jié) 因?yàn)榻Y(jié)構(gòu)體中
up
啤贩、down
、left
骇窍、right
瓜晤、都只占用一位二進(jìn)制空間,這就是 4 個(gè)二進(jìn)制空間 而 char
類型 bits
也只占用了一個(gè)字節(jié) 他們都在聯(lián)合體中 因此 共用一個(gè)字節(jié)的內(nèi)存
總結(jié): 通過掩碼進(jìn)行位運(yùn)算來增加 效率 通過聯(lián)合體結(jié)構(gòu) 可以 節(jié)省內(nèi)存空間
2腹纳、clang用法
2.1痢掠、簡(jiǎn)介
Clang是一個(gè)C語言、C++嘲恍、Objective-C足画、C++語言的輕量級(jí)編譯器。源代碼發(fā)布于BSD協(xié)議下佃牛。也是Xcode 第一的編譯器
2.2 簡(jiǎn)單使用
clang -rewrite-objc main.m -o main.cpp 把目標(biāo)文件編譯成c++文件 UIKit報(bào)錯(cuò)問題
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
xcode
安裝的時(shí)候順帶安裝了xcrun
命令淹辞,xcrun
命令在clang
的基礎(chǔ)上進(jìn)行了 一些封裝,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模擬器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手機(jī))
clang就說這么多 具體深究請(qǐng)自行查閱 注意:查看模擬器版本 以及 源文件
準(zhǔn)備一段代碼 clang 其本質(zhì)
#import <Foundation/Foundation.h>
@interface LHPerson : NSObject
{
NSString * nickName;
}
@property (nonatomic, copy) NSString *name;
@end
@implementation LHPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
clang過后 我們根據(jù)LHPerson
為深入線索俘侠,進(jìn)行搜索
struct LHPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *__strong nickName;
NSString *__strong _name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation LHPerson
static NSString * _I_LHPerson_name(LHPerson * self, SEL _cmd) { return (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_LHPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LHPerson_setName_(LHPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LHPerson, _name), (id)name, 0, 1); }
// @end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
NSObject_IMPL
又是什么象缀?
struct NSObject_IMPL {
__unsafe_unretained Class isa;
};
通過上面總結(jié)
1蔬将、一個(gè)類的聲明或創(chuàng)建 底層實(shí)現(xiàn) 就是 一個(gè)結(jié)構(gòu)體
2、Class
其實(shí)就是一個(gè)指針 指向了 objc_class
類型的結(jié)構(gòu)體
3央星、LHPerson_IMPL
結(jié)構(gòu)體中有3個(gè)成員變量 isa
和nickName
_name
4霞怀、屬性自動(dòng)生成 getter
setter
方法 并且?guī)臀覀冝D(zhuǎn)成 _name
5、nickName
并沒有生成getter
setter
6莉给、self
和SEL _cmd
為默認(rèn)參數(shù)
3毙石、isa_t聯(lián)合體
union isa_t
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
};
由上面的介紹聯(lián)合體概念可以知道,cls和bits之間是互斥的颓遏,即有cls就沒有bits,有bits就沒有cls徐矩。
initInstanceIsa
我們知道alloc的流程 最重要的 是 三部曲
1、 size = cls->instanceSize(extraBytes); ///計(jì)算 需要多少內(nèi)存空間
2叁幢、 obj = (id)calloc(1, size); // alloc 開辟內(nèi)存的地方
3滤灯、 obj->initInstanceIsa(cls, hasCxxDtor);///關(guān)聯(lián)對(duì)應(yīng)的類
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;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
這就很好地解釋了為什么上面的源碼在初始化isa的時(shí)候會(huì)用nonpointer來區(qū)分開。所以isa的大小占8個(gè)字節(jié)曼玩,64位力喷。其中這64位中分別存儲(chǔ)了什么呢?通過ISA_BITFIELD位域
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)
# else
# error unknown architecture for packed isa
# endif
這兩種是分別在arm64和x86系統(tǒng)架構(gòu)下的演训,但是都是64位的,本文的說明是在x86下介紹的贝咙。
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 位用來存儲(chǔ)類指針。x86_64 下 44 位
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。
lldb指令驗(yàn)證
x/4gx
person : 打印出地址p/x
LHPerson.class : 打印類的內(nèi)存地址
通過源碼 搜索 object_getClass
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
objc_object::getIsa()
{
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;
}
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#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
}
從源碼中可以知道返回的isa最終是(Class)(isa.bits & ISA_MASK)葵硕。
p/x
:0x00007ffffffffff8ULL & 0x001d80010000233d
最終發(fā)現(xiàn)
繼續(xù)p/x 做位移運(yùn)算
我的天 isa 指針中 真的包含 類的信息