iOS ——runtime(3):浅析NSObject对象的isa_t

分析

在上一篇文章中咱们说到过isa实际上是个联合体,那么到底什么是联合体,做者再带你们温习一下:ios

联合体
在进行某些算法的C语言编程的时候,须要使几种不一样类型的变量存放到同一段内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种几个不一样的变量共同占用一段内存的结构,在C语言中,被称做“共用体”类型结构,简称共用体,也叫联合体。算法

联合体和结构体
“联合”与“结构”有一些类似之处。但二者有本质上的不一样。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。以下面介绍的“单位”变量,如定义为一个可装入“班级”或“教研室”的联合后,就容许赋予整型值(班级)或字符型(教研室)。要么赋予整型值,要么赋予字符型,不能把二者同时赋予它。联合类型的定义和联合变量的说明:一个联合类型必须通过定义以后,才能把变量说明为该联合类型。编程

演示代码以下:bash

#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。ide

运行后的结果以下:函数

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相关的代码)后简化以下:ui

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.bitsISA_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有更深的理解。



做者:kyson

连接www.jianshu.com/p/3844f447b…

相关文章
相关标签/搜索