在对objc源码实现的探索的过程中,发现了一个很特别的实现,就是isa
。isa 是将对象内存空间与 class 之间联结起来的桥梁,而他的实现也很精妙,在有限的存储空间(一个寄存器的存储空间,在 64 位架构为 16 个字节,在 32 位的架构为 8 个字节)里,因此节省了很多内存空间。
首先,我们先了解一下联合体和位域。
联合体
我们知道结构体(Struct)是一种构造类型或复杂类型,它可以包含多个类型不同的成员。在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union)。共用体有时也被称为联合或者联合体,它的定义格式为:
union 共用体名{
成员列表
};
结构体和联合体的区别在于:结构体的各个成员会占用不同的内存空间,互相之间没有影响,处于共存关系;而联合体的所有成员占用同一段内存空间,修改一个成员的内容会影响其余所有成员,各个成员之间处于互斥关系。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
下面我们通过一段代码验证一下:
union MYUnion{
double d;
int i;
char c;
};
struct MYStruct{
double d;
int i;
char c;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
union MYUnion u = {0};
struct MYStruct s = {0};
NSLog(@"联合体u内存大小:%lu", sizeof(u));
NSLog(@"结构体s内存大小:%lu", sizeof(s));
NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
u.i = 1;
s.i = 1;
NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
u.c = 'g';
s.c = 'g';
NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
u.d = 9.9;
s.d = 9.9;
NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
}
return 0;
}
其中,在x86_64的Mac系统环境下:
-
double
占8
个字节 -
int
占4
个字节 -
char
占1
个字节
打印输出结果如下:
2020-09-11 11:51:17.505112+0800 PWDebug[60257:2817639] 联合体u内存大小:8
2020-09-11 11:51:17.506228+0800 PWDebug[60257:2817639] 结构体s内存大小:16
2020-09-11 11:51:17.506321+0800 PWDebug[60257:2817639] MYUnion d = 0.000000, c = , i = 0
2020-09-11 11:51:17.506392+0800 PWDebug[60257:2817639] MYStruct d = 0.000000, c = , i = 0
2020-09-11 11:51:17.506454+0800 PWDebug[60257:2817639] MYUnion d = 0.000000, c = �, i = 1
2020-09-11 11:51:17.506521+0800 PWDebug[60257:2817639] MYStruct d = 0.000000, c = , i = 1
2020-09-11 11:51:17.506579+0800 PWDebug[60257:2817639] MYUnion d = 0.000000, c = g, i = 103
2020-09-11 11:51:17.506639+0800 PWDebug[60257:2817639] MYStruct d = 0.000000, c = g, i = 1
2020-09-11 11:51:17.506696+0800 PWDebug[60257:2817639] MYUnion d = 9.900000, c = \315, i = -858993459
2020-09-11 11:51:17.506761+0800 PWDebug[60257:2817639] MYStruct d = 9.900000, c = g, i = 1
很明显看出,联合体u的其中一个成员(i,c,d)
被修改后,其余的成员也受到影响。而结构体s的i,c,d
三个变量是稳稳坐拥自己的内存空间,不受其他成员变量影响。
位域
什么是位域(概念)?
带有预定义宽度的变量被称为位域。
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用0和1表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种数据结构,叫做“位域”或“位段”。
位域的声明
struct
{
type [member_name] : width ;
};
元素 | 描述 |
---|---|
type | 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。 |
member_name | 位域的名称。 |
width | 位域中位的数量。宽度必须小于或等于指定类型的位宽度,而且最小值为1。 |
下面我们来看一个例子:
struct
{
unsigned int a;
unsigned int w;
unsigned int h;
} Person;
NSLog(@"%lu", sizeof(Person)); // => 12
struct
{
unsigned int a : 3;
unsigned int w : 3;
unsigned int h : 10;
} Teacher;
NSLog(@"%lu", sizeof(Teacher)); // => 4
struct
{
unsigned short a : 3;
unsigned short w : 3;
unsigned short h : 10;
} Student;
NSLog(@"%lu", sizeof(Student)); // => 2
Person
、Teacher
、Student
占用内存大小分别为12
、4
和2
,它们的成员占用内存如下图:
总结:可想而知,在实际开发过程中,如果我们真的不需要那么多的内存空间的话,使用位域的编程方式会节省一大笔内存。
isa联合体位域
那么现在我们再回到isa
的结构实现
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
就是联合体位域
,其中它有三个成员:cls
、bits
和一个结构体
。
-
cls
typedef struct objc_class *Class;
Class
是objc_class
结构体指针类型,cls
在64位架构下是占8字节。 -
bits
#ifndef _UINTPTR_T #define _UINTPTR_T typedef unsigned long uintptr_t; #endif /* _UINTPTR_T */
bits
是一个无符号长整形,占 8 个字节。 -
结构体
struct { ISA_BITFIELD; // defined in isa.h };
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
架构下:1+1+1+33+6+1+1+1+19 = 64位,8字节。
在x86_64
架构下:1+1+1+44+6+1+1+1+8 = 64位,也是8个字节。
所以,isa_t
就只是占用了8字节的内存空间。
isa_t
位域的解释图,图片来自(关联isa)
isa和类关联
未完待续...