一、联合体位域
先定义一个结构体
struct AppleCar1 {
BOOL front;
BOOL back;
BOOL left;
BOOL right;
};
打印大小
struct AppleCar1 car1;
NSLog(@"car1 == %zd",sizeof(car1));
结果
001-联合体位域[6268:355746] car1 == 4
上面可知,如果用结构体存储
4个方向,需要4字节,接下来我们通过位域方式存储
struct AppleCar2 {
BOOL front :1;
BOOL back :1;
BOOL left :1;
BOOL right :1;
};
运行打印一下
struct AppleCar1 car1;
struct AppleCar2 car2;
NSLog(@"car1 ==%zd car2 ==%zd",sizeof(car1),sizeof(car2));
结果
001-联合体位域[6333:361078] car1 ==4 car2 ==1
可知,位域更加节省空间 接下来看一个结构体
struct ApplePerson {
char *name;
int age;
double height;
};
添加断点
打印一次往下走一步
(lldb) p person1
(ApplePerson1) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p person1
(ApplePerson1) $1 = (name = "库某", age = 0, height = 0)
(lldb) p person1
(ApplePerson1) $2 = (name = "库某", age = 18, height = 0)
(lldb)
说明结构体里面的成员变量都可以赋值,再看一个数据类型共用体
union ApplePerson2 {
char *name;
int age;
double height;
};
下断点
继续上面相同步骤,打印一次往下走一步
(lldb) p person2
(ApplePerson2) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p person2 //给name赋值成功,age和height两个成员变量里面是脏数据
(ApplePerson2) $1 = (name = "库某", age = 15936, height = 2.1220036643954044E-314)
(lldb) p person2//再跑一次,给age赋值成功,name数据是空的,height里面是脏数据
(ApplePerson2) $2 = (name = "", age = 18, height = 2.1219957998584539E-314)
(lldb)
结论:虽然共用体的成员变量和结构体是一样的,但是结构体的成员变量是共存的,但共用体的成员变量是互斥的。struct特点就是全分配,不管用不用,而union特点就是只分配一个,节省内存。
二、isa分析
之前文章分析到
initIsa是对类分配内存空间时候进行了类的绑定
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
点
initIsa进去
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
再进
initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
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;
}
}
可以看到
isa = isa_t((uintptr_t)cls);点进去
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是一个共用体,再点回去看
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
...
}
参考:
这里就涉及到
nonpointer的isa,nonpointer_isa不是一个单纯的类指针,还附带有类的其他信息,因为一个类的指针有8字节,但是类对象的指针并不需要8字节*8位这么大的地址空间,剩下的空间就装了其他的数据记录,比如引用计数、是否被弱引用、关联对象、析构函数等等关键信息...,再回到isa_t里面
#include "isa.h"
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
};
点击struct里面的
ISA_BITFIELD,这里贴出了isa源码中arm64位架构,查看位域
# 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)
参考:
| 参数名 | 用法 | 位域大小 | 位置 |
|---|---|---|---|
| nonpointer | 表示是否对纯isa指针 0:单纯isa指针 1:非单纯isa指针,不止存类对象地址,isa 中包含了类信息、对象的引用计数等等信息 | 1 | 0 |
| has_assoc | 关联对象 0:没有 1:存在 | 1 | 1 |
| has_cxx_dtor | 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象 | 1 | 2 |
| shiftcls | 存储类指针的值。x86_64架构用44位c存储,arm64架构中用33位用存储类指针。 | 33 | 3~35 |
| magic | 用于调试器判断当前对象是真的对象还是没有初始化的空间 | 6 | 36~41 |
| weakly_referenced | 是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。 | 1 | 42 |
| deallocating | 判断是否正在释放内存 | 1 | 43 |
| has_sidetable_rc | 是否需要用到外挂引用计数,当对象引用技术大于 10 则需要借用该变量存储进位 | 1 | 44 |
| extra_rc | 表示引用计数 | 19 | 45~63 |
从上面看,最重要的数据就是shiftcls,类的指针地址,接下来我们通过对象打印地址来获取isa地址,打个断点
打印一下
(lldb) x/4gx p
0x106017d20: 0x01000001000082e1 0x0000000000000000
0x106017d30: 0x0000000000000000 0x0000000000000000
(lldb)
可以看到
isa地址是0x01000001000082e1
再打印一下类的地址
(lldb) p/x ApplePerson.class
(Class) $17 = 0x00000001000082e0 ApplePerson
这里我们要从
nonpointer_isa地址0x01000001000082e1得到类的地址0x00000001000082e0,这就要用到isa掩码
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
把isa地址和isa掩码
&一下
(lldb) p/x 0x01000001000082e1 & 0x0000000ffffffff8ULL
(unsigned long long) $1 = 0x00000001000082e0
用掩码得出了类的地址,都是
0x00000001000082e0和p/x ApplePerson.class得出的地址是一致的
我们通过非纯isa还原shiftcls的反过程,重跑一次断点
(lldb) x/4gx p
0x106017d20: 0x01000001000082e1 0x0000000000000000
0x106017d30: 0x0000000000000000 0x0000000000000000
(lldb)
接下来看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)
shiftcls前面有3位,后面28位,我们可以通过平移得到中间的33位,先右移3位,再左移31位,最后右移28位回到原来位置得出33位的指针地址
(lldb) x/4gx p
0x1005ba390: 0x01000001000082e1 0x0000000000000000
0x1005ba3a0: 0x75636f44534e5b2d 0x69766552746e656d
(lldb) p/x 0x01000001000082e1 >> 3
(long) $12 = 0x002000002000105c
(lldb) p/x 0x002000002000105c << 31
(long) $13 = 0x1000082e00000000
(lldb) p/x 0x1000082e00000000 >> 28
(long) $14 = 0x00000001000082e0
通过指针平移,我们可知shiftcls地址是0x00000001000082e0
,po 0x00000001000082e0可以得到类
(lldb) po 0x00000001000082e0
ApplePerson
接下来我们再验证一下
(lldb) p/x ApplePerson.class
(Class) $15 = 0x00000001000082e0 ApplePerson
同样得到
0x00000001000082e0这个地址,完全一致