本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
分析
上一篇文章中我们说到isa其实是个联合体,那什么是联合体,笔者再带大家温习一下:
联合体
在进行某些算法的C语言编程的时候,需要使几种不同类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构,简称共用体,也叫联合体。
联合体和结构体
“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。如下面介绍的“单位”变量,如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符型(教研室)。要么赋予整型值,要么赋予字符型,不能把两者同时赋予它。联合类型的定义和联合变量的说明:一个联合类型必须经过定义之后,才能把变量说明为该联合类型。
演示代码如下:
#include<iostream>
using namespace std;
union U1
{
int n;
char s[11];
double d;
};
union U2
{
int n;
char s[5];
double d;
};
int main()
{
U1 u1;
U2 u2;
cout<<sizeof(u1)<<'\t'<<sizeof(u2)<<endl;
cout<<"u1各数据地址:\n"<<&u1<<'\t'<<&u1.d<<'\t'<<&u1.s<<'\t'<<&u1.n<<endl;
cout<<"u1各数据地址:\n"<<&u2<<'\t'<<&u2.d<<'\t'<<&u2.s<<'\t'<<&u2.n<<endl;
}
上述代码中:
对于U1联合体,s占11字节,n占4字节,d占8字节,因此其至少需1字节的空间。然而其实际大小并不是11,用运算符sizeof测试其大小为16。这是因为这里存在字节对齐的问题,11既不能被4整除,也不能被8整除。因此补充字节到16,这样就符合所有成员的自身对齐了。从这里可以看出联合体所占的空间不仅取决于最宽成员,还跟所有成员有关系,即其大小必须满足两个条件:
- (1)大小足够容纳最宽的成员;
- (2)大小能被其包含的所有基本数据类型的大小所整除。
对于U2联合体,同理知道,用运算符sizeof测试其大小为8。
运行后的结果如下:
16 8
u1各数据地址:
0x7ffeefbff608 0x7ffeefbff608 0x7ffeefbff608 0x7ffeefbff608
u1各数据地址:
0x7ffeefbff5d0 0x7ffeefbff5d0 0x7ffeefbff5d0 0x7ffeefbff5d0
Program ended with exit code: 0
上篇文章中,我们比对两个类是否相等最终判断了
isa.bits & ISA_MASK
的值是否相等。那为什么判断这两个的值是否相等即可呢,这即是本文讨论的话题。
首先浏览一下isa源码:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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
struct {
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
// SUPPORT_PACKED_ISA
#endif
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2
# define ISA_INDEX_IS_NPI 1
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15;
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;
# define RC_ONE (1ULL<<25)
# define RC_HALF (1ULL<<6)
};
# else
# error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif
};
去掉注释,以及其他平台的兼容性代码(主要是x86_64相关的代码)后简化如下:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
};
初步看到isa_t
的时候,相信大家还是比较难以理解:
- 结构体后的冒号是什么意思
-
# define ISA_MASK 0x0000000ffffffff8ULL
定义的数字的含义
1.冒号是位域
位域
位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
首先大家需要知道,不管X86还是arm的处理器都是64位的。16位操作系统中,int 占16位;在32位操作系统中,int 占32位。但是后来人们已经习惯了 int 占32位,因此在64位操作系统中,int 仍为32位。64位整型用 long long 或者64位即8个字节,即64位。
在文章结构体对齐(图解)与位域 中大家可以了解到位域对于结构体的大小起到一定的作用。
因此我们不难理解:isa_t中的bits占用了64位的数据。
上一篇文章中的
isa.bits & ISA_MASK
中
# define ISA_MASK 0x0000000ffffffff8ULL
我们来看一下,这个0x0000000ffffffff8ULL
换算成二进制
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
有31位都是1。 对isa.bits
和ISA_MASK
进行与操作,会发生什么“化学反应”呢?
位操作符
位操作符包括:&(按位与)、|(按位或)、^(按位异或)。这三个操作符非常简单,需要注意的是,这三个操作符操作的必须是整数。
这里以&为例:
当&两边是bool类型的值时,该运算符作为逻辑运算符。作用如下:
当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
当&两边不是bool类型的时候,该运算符作为位运算符,将两边的值作为二进制展开,依次对每一位进行 按位与。作用如下:
11100101 & 01011010 = 01000000
经过以上分析,我们不难得出:
上图中可以看出ISA_MASK的值转化为二进制中有33位都为1,上面的例子可以看出,按位与的作用是可以取出这33位中的值。那么此时很明显了,同ISA_MASK进行按位与运算即可以取出Class的值。
写到这里,我们再回头看看isa_t的源码,不难发现,这33位对应的是结构体的shiftcls
的位域。其他的位域在最后也一并做个预习吧:
struct {
// 1代表优化后的使用位域存储更多的信息。
uintptr_t nonpointer : 1;
// 是否有设置过关联对象
uintptr_t has_assoc : 1;
// 是否有C++析构函数
uintptr_t has_cxx_dtor : 1;
// 存储着Class对象的内存地址信息
uintptr_t shiftcls : 33;
// 用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6;
// 是否有被弱引用指向过。
uintptr_t weakly_referenced : 1;
// 对象是否正在释放
uintptr_t deallocating : 1;
// 引用计数器是否过大无法存储在isa中
// 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
uintptr_t has_sidetable_rc : 1;
// 里面存储的值是引用计数器减1
uintptr_t extra_rc : 19;
};
总结:
本文从isa的bits出发,总结了NSObject对象的联合体isa
的部分字段的含义。希望大家对isa有更深的理解。
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
广告
我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。