從 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.