从 isa 底层结构引入联合体、位域
在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_t
是一个 union
(共用体/联合体),联合体意味着公用内存 , 也就是说 isa
其实总共还是占用 8
个字节内存 , 共 64
个二进制位 。
其中 ISA_BITFIELD
(位域) 宏定义在不同架构下表示如下 :
# if __arm64__
# 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
# elif __x86_64__
# 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
- 由于联合体的特性 ,
cls
,bits
以及struct
都是8
字节内存 , 也就是说他们在内存中是完全重叠的。- 实际上在
runtime
中,任何对struct
的操作和获取某些值,如extra_rc
,实际上都是通过对bits
做位运算实现的。bits
和struct
的关系可以看做 :bits
向外提供了操作struct
的接口,而struct
本身则说明了bits
中各个二进制位的定义。
接下来我们深入研究一下联合体、位域。
联合体
- 结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
- 结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
// 联合体
union {
char bits;
// 位域
struct { // 0000 1111
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
} _direction;
- 联合体中可以定义多个成员,联合体的大小由最大的成员大小决定
- 联合体的成员公用一个内存,一次只能使用一个成员
- 对某一个成员赋值,会覆盖其他成员的值
- 存储效率更高,可读性更强,可以提高代码的可读性,可以使用位运算提高数据的存储效率
位域
结构体中除了可以定义基本数据类型外,还可以使用位域来构建数据成员,也就是说某个数据成员可能只占用结构体中某几个bit位的存储空间。结构体中定义位域的目的主要是为了节省内存空间。假如某个结构体中有 8
个 BOOL
类型的数据成员用来描述 8
种状态。那么我们需要定义 8
个 BOOL
类型的数据成员,这样这个结构体实例就占用了 8
个字节的内存空间,而如果我们使用 位域
来定义的话则可以用一个字节的内存空间就可以表述出来。定义 位域
的格式如下:
struct {
// 位域名 : 位域长
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
- 位结构中的成员可以定义为
unsigned
, 也可定义为signed
或者是char
, 但当成员长度为1
时, 会被认为是unsigned
类型。因为单个位不可能具有符号。 - 位结构中的成员不能使用数组和指针, 但位结构变量可以是数组和指针, 如果是指针, 其成员访问方式同结构指针。
- 位结构总长度(位数), 是各个位成员定义的位数之和(如果类型相同的话)
- 位结构成员可以与其它结构成员一起使用。
- 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过
8
位二进位。 - 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
- 如果相邻位域字段的类型相同,且其位宽之和小于类型的
sizeof
大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止。
- 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过
#include <stdio.h>
struct test {
char a : 2;
char b : 3;
char c : 1;
};
struct test1 {
char a : 2;
char b : 3;
char c : 7;
};
struct {
short int a:2;
int b:4;
char c;
}test2;
struct {
int a:2;
int b:4;
char c;
}test3;
struct {
short s1:3;
short s2:3;
short s3:3;
}test4;
struct{
char c1:3;
char c2:2;
char c3:2;
}test5;
struct test6
{
int a:4;
int b:3;
char c;
};
int main(int argc, const char * argv[]) {
// insert code here...
printf("test长度:%d\ntest1长度:%d\ntest2长度:%d\ntest3长度:%d\ntest4长度:%d\ntest5长度:%d\ntest6长度:%d\n",sizeof(struct test),sizeof(struct test1),sizeof(test2),sizeof(test3),sizeof(test4),sizeof(test5),sizeof(struct test6));
return 0;
}
控制台输出:
test长度:1
test1长度:2
test2长度:4
test3长度:4
test4长度:2
test5长度:1
test6长度:4
Program ended with exit code: 0
小测验:
32位环境下,给定结构体
Struct A
{
Char t:4;
Char k:4;
Unsigned short i:8;
Unsigned long m;
};
问 sizeof ( A )
=_____;
A. 6
B. 7
C. 8
D. 上述答案都不对
变量后面加 : 然后加数字表示位域,也就是说着代表按位来存放的,不是按字节,这是计算机为了节约空间的一种方式。char是一个字节(8个位),所以 t和k 加起来刚好8个位,也就是一个字节。然后short 一共16个位放了8个,剩下8个不够后面long存放,所以算两个字节。因为long在32是4个字节,所以一共 1 +2 +4 = 7 。然后进行结构体对齐,所以就是8.