文章目录
- 一、clang初探
- 1.clang简介
- 2.使用clang生成底层实现源码
- 3.clang底层初探
- 二、malloc底层原理
- 三、联合体和位域
- 四、initIsa
- 五、nonpointer isa
- 六、isa推导class
- 七、isa的位运算
- 八、总结
一、clang初探
1.clang简介
-
clang
是一个C语言、C++
、Objective-C
语言的轻量级编译器。源代码发布于BSD协议下; -
Clang
将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。 -
Clang
是一个由Apple主导编写,基于LLVM
的C/C++/Objective-C
编译器
2013年4月,Clang已经全面支持C++11标准,并开始实现C++1y特性(也就是C++14,这是C++的下一个更新版本)。Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。 - Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的
C/C++/Objective-C/Objective-C++
编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容,包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载(通过__attribute_((overloadable))
来修饰函数),其目标(之一)就是超越GCC。
2.使用clang生成底层实现源码
- 把目标文件main.m编译成main.cpp C++文件
clang -rewrite-objc main.m -o main.cpp
- 如果导入了UIKit库、则需要添加附带参数
clang -rewrite-objc -fobjc-arc -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
- Xcode安装时会附带安装xcrun命令,xcrun命令是在clang基础上进行了一些封装,要更好用一些
- 生成模拟器环境下的底层代码
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
3.clang底层初探
- 创建macOS - Command Line Tools工程、编写HSPerson类、
#import <Foundation/Foundation.h>
@interface HSPerson : NSObject
@end
@implementation HSPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
- 查看clang后的编译底层源码
clang -rewrite-objc main.m -o main.cpp && open main.cpp
- 首先在main.cpp文件中查找我们的 HSPerson、
#ifndef _REWRITER_typedef_HSPerson
#define _REWRITER_typedef_HSPerson
typedef struct objc_object HSPerson;
typedef struct {} _objc_exc_HSPerson;
#endif
struct HSPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
- 底层实现可以看到、对象在底层的本质就是一个结构体 struct
struct HSPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
- 于此同时、在HSPerson类中增加一个属性
@interface HSPerson : NSObject
@property (nonatomic,strong)NSString *hsName;
@end
- 再次生成底层实现代码、可以看到增加了部分代码;如此就鲜明的表现了HSPerson类在底层确实是一个结构体
extern "C" unsigned long OBJC_IVAR_$_HSPerson$_hsName;
struct HSPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_hsName;
};
- 下面我们看结构体中嵌套的结构体
NSObject_IMPL
- 在OC上层中我们的
HSPerson
是继承于NSObject
的、 - 然而在底层实现中、结构体不存在继承关系、只能理解为伪继承关系。
- 那么
NSObject_IVARS
是什么呢? 其中的关键字为IVARS
、是不是isa呢?
查找NSObject_IMPL
的定义
struct NSObject_IMPL {
Class isa;
};
- 一目明了、说明 HSPerson类底层实现中包含了一个 isa变量。
- 回到底层HSPerson定义处
typedef struct objc_object HSPerson;
- 在OC层面HSPerson继承于NSObject、而NSObject在CPP底层则体现为
objc_object
;
typedef struct objc_class *Class;
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
- 同理 往上瞄一眼、
- OC层面的Class在CPP底层是以什么形式存在呢?
- 是不是就是
objc_class
了
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa __attribute__((deprecated));
} __attribute__((unavailable));
- Class是一个结构体指针、这也就是为什么我们在OC上层使用Class的时候不需要 * 的原因
- 与此同时
id类型在底层是什么表现形式呢?
typedef struct objc_object *id;
- 这就是申明一个对象时、id类型不加 * 的原因、因为本身就是指针类型。
- 继续看HSPerson相关的内容
// @implementation HSPerson
static NSString * _I_HSPerson_hsName(HSPerson * self, SEL _cmd) {
//(char *)self指针地址 + OBJC_IVAR_$_HSPerson$_hsName偏移指针地址
//对两者所取到的值进行类型强转拿到(NSString **)值
//返回(NSString **)值的指针地址
return (*(NSString **)((char *)self + OBJC_IVAR_$_HSPerson$_hsName));
}
static void _I_HSPerson_setHsName_(HSPerson * self, SEL _cmd, NSString *hsName) {
(*(NSString **)((char *)self + OBJC_IVAR_$_HSPerson$_hsName)) = hsName;
}
// @end
- 这里将其格式化展开,是不是明显的就是我们hsName属性的getter方法和setter方法在底层的实现
- 我们在OC上层并没有看到getter方法中
(HSPerson * self, SEL _cmd)
参数和setter方法中的(HSPerson * self, SEL _cmd, NSString *hsName)
参数;这就是我们的隐藏参数;
下边的内容我们先混个脸熟、以后再深入探索
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods;
const struct method_list_t *class_methods;
const struct method_list_t *optionalInstanceMethods;
const struct method_list_t *optionalClassMethods;
const struct _prop_list_t * properties;
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
二、malloc底层原理
- 下载可调试源码、创建工程
- maloc切入点
obj = (id)calloc(1, size);
- 难点分析文档、查看掘金文档、暂时不想分析。
三、联合体和位域
- 我们在探索创建对象的过程中、最终会来到
_class_createInstanceFromZone
方法、需要详细探索的点还有initInstanceIsa
和initIsa
、可以看到initInstanceIsa
函数中除去两个断言、也都是调用了initIsa
、区别在于initIsa
参数中nonpointer
赋值是否为true。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
- 在探索 initIsa之前、我们需要先了解两个概念、联合体和位域
案例分析:先查看下两个代表汽车前进方向的结构体
struct HSCar1{
BOOL front;
BOOL back;
BOOL left;
BOOL right;
}car1;
struct HSCar2{
BOOL front :1;
BOOL back :1;
BOOL left :1;
BOOL right :1;
}car2;
- 上述HSCar1结构体占用的内存是多少?
- 经过前面的学习、我们可以看出car1占用内存为4字节
- 经过sizeof输出验证结果
NSLog(@"%lu",sizeof(hsChar1));//4
- 对于我们想要前进的方向来说、car1中假设我们想要往前、那么front的值也就只有 0和1。对于car1来说占用了 4字节x8= 32位;但是对于数据存储来说我们并不需要32位来存储四个BOOL类型的数据、
0000 0000 0000 0000 0000 0000 0000 1111
- 假设四个BOOL类型数据全为true、也只占用了4位。实际上只需要4位就够了、也就是半个字节、实际上并没有半个字节、最小一个字节单位、最优解也就是一个字节就够了。因此实际上这就造成了资源的浪费、浪费了三倍的空间。
- 此时此刻为了节省资源空间、引入位域这个概念、也就是HSCar2的形式、冒号后表示设置的位数;front:1; 表示front占用了1位。
- 再输出car2的占用内存大小、得到其占用了一个字节、这就大大减少了资源的浪费。
NSLog(@"%lu",sizeof(car2));//1
- 那么假设我们设置其中所占用的位数增大、会不会增大占用的内存呢?
struct HSCar2{
BOOL front:1;
BOOL back:2;
BOOL left:6;
BOOL right:1;
}car2;
- 此时car2整体占用了10位、那么它所占用的内存空间有多大?讲道理的来说它应该占用2个字节、假设如下表示其占位
// 0000 00 0 000000 00 0
// 空位 空位 right left back front
NSLog(@"%lu",sizeof(car2));//2
- 然而当一辆车运行的过程中、向前的同时是不能向后的、所以就产生了互斥效应。
- 关于互斥、我们来看引入另一个案例
struct HSTeacher1{
char *name;
int age;
double height;
}teacher1;
- 当我们对teacher1进行赋值的时候、依次可以看到在未赋值的情况下、默认值都是正常值
int main(int argc, const char * argv[]) {
teacher1.name = "Holothurian";//断点
teacher1.age = 18; //断点
NSLog(@"%lu",sizeof(teacher1));//断点
return 0;
}
(lldb) p teacher1
(HSTeacher1) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p teacher1
(HSTeacher1) $1 = (name = "Holothurian", age = 0, height = 0)
(lldb) p teacher1
(HSTeacher1) $2 = (name = "Holothurian", age = 18, height = 0)
- 此时此刻、我们想要看到的互斥现象并没有产生、这就需要引入一个叫做联合体的概念了、下面查看联合体的案例
union HSTeacher2{
char *name;
int age;
double height;
};
- 在main函数中初始化并且赋值、打上断点、依次输出结果
union HSTeacher2 teacher2;
teacher2.name = "Holothurian";
teacher2.age = 18;
- 可以看到在初次赋值后、就出现了脏数据、脏内存、表明了在同一时间只有一个成员能够被使用、这就体现了互斥的概念。
(lldb) p teacher2
(HSTeacher2) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p teacher2
(HSTeacher2) $1 = (name = "Holothurian", age = 15970, height = 2.1220036811936364E-314)
(lldb) p teacher2
(HSTeacher2) $2 = (name = "", age = 18, height = 2.1219957998584539E-314)
(lldb)
综上总结
- 结构体struct中所有变量遵循“共存”原则、优点是有容乃大、全面;缺点是struct内存空间的分配是粗放的,不管用不用,全部分配;
- 联合体union中各变量是“互斥”的,缺点是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间;
四、initIsa
- 接下来我们查看
initIsa
、initIsa
使内存申请的结构体指针和Class
绑定在一起。去除ASSERT
部分。
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
#if SUPPORT_INDEXED_ISA
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
- 从函数最后可以看到、所有的操作都是为了最后的赋值
isa = newisa
;而newisa
的来源在于isa_t
;下面我们来查看 isa_t 的结构
union isa_t {
//构造方法
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//属性bits
uintptr_t bits;
private:
//访问类需要自定义 ptrauth 操作,因此强制客户端使用私有成员来操作 setClass/getClass
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
五、nonpointer isa
- 上面的结构可以看出isa_t本质是个union联合体/共用体; 我们普遍在表现类的地址的时候经常看到一个
nonpointer isa;
那么什么是nonpointer isa
呢?
- 类也是一个对象、也就是一个指针、类上的很多信息都是可以存储的;一个指针具有8字节、也就是8x8 = 64位;
- 如果64位只是存储一个指针,空间就大大的浪费了、所以我们需要存储一些与类息息相关的信息来优化这片存储空间。
- 比如说:是否正在释放、引用计数、是否有weak弱饮用、是否存在关联对象、是否有析构函数等
- 所以出现了
nonpointer isa
概念、意味着我们可以将类相关的信息存在64位信息中,不再是一个简单的指针地址。 - 接下来我们需要看isa_t中存储的信息,去验证存储的信息、怎么看呢?
- 经过前面的铺垫、其实我们需要看的是位域部分的设置、也就是
ISA_BITFIELD
中的信息了
struct {
ISA_BITFIELD; // defined in isa.h
};
- 对于x86_64来说、我们可以看到其中的位域设置信息如下
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
- 对于arm64结构来说、其中的位域信息如下
# if __arm64__
// ARM64 模拟器具有更大的地址空间,因此即使模拟器是为 ARM64-not-e 构建的,也要使用 ARM64e 方案。
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; //是否对指针开启指针优化、0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。 \
uintptr_t has_assoc : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t shiftcls_and_sig : 52; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD
uintptr_t nonpointer : 1; //是否对指针开启指针优化、0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。
uintptr_t has_assoc : 1; //关联对象标志位,0没有,1存在
uintptr_t has_cxx_dtor : 1; //是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
uintptr_t shiftcls : 33; //存储类指针的地址值。/*MACH_VM_MAX_ADDRESS 0x1000000000*/
uintptr_t magic : 6; //用于调试器判断当前对象是真的对象还是没有初始化的空间。
uintptr_t weakly_referenced : 1; //是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
uintptr_t unused : 1; //是否正在释放对象。
uintptr_t has_sidetable_rc : 1; //如果对象引用计数大于10时,则需要借用该变量存储进位
uintptr_t extra_rc : 19 //当表示该对象的引用计数值,实际上是引用计数减1。
//如果对象的引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用has_sidetable_rc。
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
上述位域中的关键信息及对应的解释
- nonpointer: 表示是否对指针开启指针优化、0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。
has_assoc
: 关联对象标志位,0没有,1存在has_cxx_dtor
: 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。shiftcls
: 存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针。magic
: 用于调试器判断当前对象是真的对象还是没有初始化的空间。weakly_referenced:
标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。deallocating
: 标志对象是否正在释放对象。has_sidetable_rc
:当对象引用计数大于10时,则需要借用该变量存储进位extra_rc
: 当表示该对象的引用计数值,实际上是引用计数减1。如果对象的引用计数为10,那么extra_rc
为9。如果引用计数大于10,则需要使用has_sidetable_rc
。
六、isa推导class
- 对象指针占用8字节、那么我们来调试查看对象地址中所存储的信息
- 创建Command Line Tools 工程、编写一个HSPerson类、然后在main函数中调用
- 此时此刻我们可以看到格式化输出person对象的地址信息;其中 0x001d8001000082dd 表示最高两位为00、那么存储信息则差距很大
(lldb) p 0x1000000000000000 - 0x00100000000000000
(long) $1 = 1080863910568919040
(lldb) p/x 0x1000000000000000 - 0x00100000000000000
(long) $2 = 0x0f00000000000000
(lldb) p/t 0x001d8001000082dd
(long) $3 = 0b0000000000011101100000000000000100000000000000001000001011011101
(lldb)
- x/4gx 输出空间存储信息
- p/x 打印指针首地址
- p/t 按照二进制打印地址存储信息
- p *$0 获取$0指针首地址上的信息
- 前面 x/4gx person的输出结果来看、意味着我们关联到了类的信息;通过 p/x HSPerson.class 可以得到类的地址
(lldb) x/4gx person
0x10058d530: 0x001d8001000082dd 0x0000000000000000
0x10058d540: 0x0000000000000000 0x0000000000000000
(lldb) p/x HSPerson.class
(Class) $4 = 0x00000001000082d8 HSPerson
- 那么类的地址是怎么和我们的对象地址进行关联的呢?
- 下面我们引入
ISA_MASK
掩码的概念:它就相当于一个面具、用来获取对象地址上类的信息; - 通过isa_t的位域部分、我们知道shiftcls是我们想要获取的类的存储信息的地方、此时运行在x86_64架构下、看看掩码所在的二进制位、
- 从第3位到第46位,44位二进制位上都是1,都是掩码所在位置;而ISA_BITFIELD位域信息中所占的位数是44位、其他信息会按照顺序依次排放;为了取出对象地址上的shiftcls信息、是不是只需要 & ISA_MASK 就可以了? 这也就是面具存在的意义。
-
0x001d8001000082dd
为person对象的地址、0x00000001000082d8
为HSPerson类的信息地址;ho address
为获取堆栈MachO地址信息插件
(lldb) p/x 0x001d8001000082dd & 0x00007ffffffffff8ULL
(unsigned long long) $9 = 0x00000001000082d8
(lldb) po 0x00000001000082d8
HSPerson
(lldb) ho address 0x00000001000082d8
address : 0x00000001000082d8, 28HSPerson <+0> ,(OBJC_CLASS_$_HSPerson),External: YES HSPerson.__DATA.__objc_data +28
(lldb)
- 综上:对象地址 & isa掩码就可以得到类的信息
- 回到
objc_object::initIsa
中,如果当前对象没有开启指针优化:不是nonpointer isa
,则直接setClass
赋值操作。如果开启了指针优化、则对其进行了逐个赋值操作 - 其中setClass(cls,this)做了什么操作呢?
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj){
//匹配条件
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
// 使用原生指针
uintptr_t signedCls = (uintptr_t)newCls;
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
// 我们只对 Swift 类进行签名。 非Swift类只使用原始指针
uintptr_t signedCls = (uintptr_t)newCls;
if (newCls->isSwiftStable())
signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
// 我们正在签署一切
uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# else
# error Unknown isa signing mode.
# endif
//签名后的类进行右移3位操作
shiftcls_and_sig = signedCls >> 3;
#elif SUPPORT_INDEXED_ISA
//Indexed isa 仅使用此方法来设置原始指针类。设置索引类是单独处理的。
cls = newCls;
#else //非指针优化 isa,没有 ptrauth、直接右移3位操作
shiftcls = (uintptr_t)newCls >> 3;
#endif
}
七、isa的位运算
- 经过前面的推导、实际上在不知道掩码的情况下、我们想要获取0x001d8001000082dd 对象地址上的类信息、只需要进行位运算操作即可
- 以x86_64架构为例、64位信息中 中间44位为类信息、总64位信息结构为 3 + 44 + 17 = 64.
- 只需要我们右移3位抹零低三位、再回到原位置、左移17位抹零高17位、再回到原位置即可
(lldb) p/x 0x001d8001000082dd >> 3 << 3 << 17 >> 17
(long) $18 = 0x00000001000082d8
(lldb) po 0x00000001000082d8
HSPerson
- 也可以先抹零高17位、再抹零低3位;连贯起来也就是左移17位、右移20位、左移3位归位;
(lldb) p/x 0x001d8001000082dd << 17 >> 20 << 3
(long) $20 = 0x00000001000082d8
(lldb) po $20
HSPerson
八、总结
- 通过clang把目标文件编译成底层代码
- main.m编译成main.cpp C++文件
clang -rewrite-objc main.m -o main.cpp
- 如果导入了UIKit库、则需要添加附带参数
clang -rewrite-objc -fobjc-arc -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
- cpp底层通过self+内存平移来获取
setter
方法和getter
方法 - 对象和isa的关系:对象底层含有isa指针、isa指向对象的存储信息
- 联合体和位域:
- 结构体struct中所有变量遵循“共存”原则、优点是有容乃大、全面;缺点是struct内存空间的分配是粗放的,不管用不用,全部分配;
- 联合体union中各变量是“互斥”的,缺点是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间;
-
nonpointer isa
:如果开启了指针优化、可以将类相关的信息存在对象的指针64位信息中,不再是一个简单的指针地址。ISA_BITFIELD
中的位域位数将成为探索其中信息的关键点 isa
推导class
:
- 对象地址 &
isa
掩码就可以得到类的信息 - 对象地址经过位平移操作可以得到类信息