(1)基礎
(1.1)背景
有些信息在存儲時,並不需要佔用一個完整的字節, 而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有0和1 兩種狀態,用一位二進位即可。爲了節省存儲空間,並使處理簡便,C語言又提供了一種數據結構,稱爲“位域”或“位段”。
(1.2)定義
所謂“位域”是把一個字節中的二進位劃分爲幾個不同的區域,並說明每個區域的位數。每個域有一個域名,允許在程序中按域名進行操作。 這樣就可以把幾個不同的對象用一個字節的二進制位域來表示。
(1.3)格式
位域定義與結構定義相仿,其形式爲:
struct 位域結構名
{
類型說明符 位域名:位域長度(位寬)
......
};
比如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
(2)存儲
(2.1)結構體的存儲規則
(2.1.1)結構體的對齊規則:
1)結構體的每一個成員變量的首地址要能被自身所佔的內存大小所整除;
2)結構體的大小要是最長成員變量類型的整數倍;
即:結構體遵循對齊原則,以最長的成員變量類型的長度對齊。
比如:
struct A
{
char ch1;
char ch2;
int a;
char ch3;
}a;
printf ("sizeof(a) = %d\n",sizeof(a));
成員大小爲12;
爲什麼要對齊呢?因爲以取int型數據,32位系統是以4字節取數據的,如果不對齊,那麼可能取一個int型的數據,系統要取兩次,這影響了效率。當然,內存對齊提高了運行效率,但也犧牲了一部分空間(有空隙),這就是魚和熊掌不可兼得。
(2.1.2)__packed__設置結構體對齊係數
struct pp2_tlv_ns {
uint8_t type;
uint8_t len_hi;
uint8_t len_lo;
uint8_t padding;
uint32_t vni;
uint32_t vlb;
} __attribute__((__packed__));
_attribute__((__packed__)) 按照實際的結構體的大小來算,不考慮對齊;
(2.1.3)設置全局的對齊係數
每個特定平臺上的編譯器都有自己默認的“對齊係數”,我們可以通過預處理指令#pragma pack(n), n=1, 2, 4, 8, 16...來改變這一系數,這個 n 就是對齊係數
1)數據成員對齊規則:
結構(struct)或聯合(union)的數據成員,第一個數據成員放在 offset 爲 0 的地方,以後的每個數據成員的對齊按照#pragma pack(n)指定的 n 值和該數據成員本身的長度 len = sizeof(type) 中,較小的那個進行,如果沒有顯示指定n值,則以len爲準,進行對齊
2)結構/聯合整體對齊規則:在數據成員對齊完成之後,結構/聯合本身也要對齊,對齊按照#pragma pack(n)指定的n值和該結構/聯合最大數據成員長度max_len_of_members中,較小的那個進行,如果沒有顯示指定n值,則以max_len_of_members爲準,進行對齊
結合1、2可推斷:當n值均超過(或等於)所有數據成員的長度時,這個n值的大小將不產生任何效果;
範例:
struct{
char a;
double b;
int c;
} test;
結構體的長度就是 24 個字節;
#pragma pack(4)
struct{
char a;
double b;
int c;
} test;
對於成員a,對齊數爲 1,因爲 1 小於 4,放在偏移量爲 0 的位置上,然後長度變爲 1
對於成員b,對齊數爲 4,因爲 4 小於 8,放在偏移量爲 4 的位置上,長度變爲 4 + 8 = 12
對於成員c,對齊數爲 4,兩個數相等,放在偏移量爲 12 的位置上,長度變爲 12 + 4 = 16
最後是結構體,最大的成員長度 8,大於 4,所以取 4,剛好整除,所以就是 16 字節
(2.2)位域的存儲規則
1) 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則後面的字段將緊鄰前一個字段存儲,直到不能容納爲止;
2) 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則後面的字段將從新的存儲單元開始,其偏移量爲其類 型大小的整數倍;
3)位域的寬度不能超過它所依附的數據類型的長度。
通俗的講,成員變量都是有類型的,這個類型限制了成員變量的最大長度,:後面的數字不能超過這個長度。
4)只有有限的幾種數據類型可以用於位域。
在 ANSI C 中,這幾種數據類型是 int、signed int 和 unsigned int(int 默認就是 signed int);到了 C99,_Bool 也被支持了。但編譯器在具體實現時都進行了擴展,額外支持了 char、signed char、unsigned char 以及 enum 類型,所以上面的代碼雖然不符合C語言標準,但它依然能夠被編譯器支持。
5) 整個結構體的總大小爲最寬基本類型成員大小的整數倍。
即整體結構體的大小是max{最寬位域數據類型,非位域數據類型}的整數倍;
6)不可以獲取位域成員的地址
位域成員往往不佔用完整的字節,有時候也不處於字節的開頭位置,因此使用&
獲取位域成員的地址是沒有意義的,C語言也禁止這樣做。地址是字節(Byte)的編號,而不是位(Bit)的編號。
7) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式,GCC採取壓縮方式;
8) 如果位域字段之間穿插着非位域字段,則不進行壓縮;
注意:4 ,5跟編譯器有較大的關係,使用時要慎重,儘量避免。
(2.3)無名位域
位域成員可以沒有名稱,只給出數據類型和位寬,如下所示
struct bs{
int m: 12;
int : 20; //該位域成員不能使用
int n: 4;
};
作用:
無名位域一般用來作填充或者調整成員位置。因爲沒有名稱,無名位域不能使用。
上面的例子中,如果沒有位寬爲 20 的無名成員,m、n 將會挨着存儲,sizeof(struct bs) 的結果爲 4;有了這 20 位作爲填充,m、n 將分開存儲,sizeof(struct bs) 的結果爲 8。
(3)位域的使用
位域的使用和結構成員的使用相同,其一般形式爲:
結構體變量名.位域名
位域允許用各種格式輸出。
main(){
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d,%d,%d\n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);
}
(4)範例
(4.1)範例一
struct test1
{
char a:1;
char :2;
long b:3;
char c:2;
};
int len = sizeof(test1);
對於上述例子,len的值應該是12.解釋如下:
首先以最長的類型位寬做爲偏移量,最長的是long型,佔4B,所以不同類型之間應該是4個字節的偏移,即test1應該是4字節的整數倍。
char a:1; //用一個字節去存儲
char :2; //空域。因爲與前面的a的類型相同,而兩個位域的位寬相加仍然少於8位,所以依然用1個字節表示
long b:3; //long類型的位寬是4個字節,與前面的char類型不同,所以b與a之間偏移4個字節,它們之間自動補充3個字節
char c:2; //因爲c與b又不同型,以test1中的最長的long類型的位寬進行偏移,所以雖然char只用1個字節就夠了
//但依然要佔4個字節。
總共是12字節。
(4.1)範例二
struct s1
{
int i: 8;
int j: 4;
int a: 3;
double b;
};
struct s2
{
int i: 8;
int j: 4;
double b;
int a:3;
};
printf("sizeof(s1)= %d\n", sizeof(s1));
printf("sizeof(s2)= %d\n", sizeof(s2));
result: 16, 24
(4.3)範例三
int main(){
struct bs{
unsigned m: 6;
unsigned n: 12;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
return 0;
}
運行結果: 4
m、n、p 的類型都是 unsigned int,sizeof 的結果爲 4 個字節(Byte),也即 32 個位(Bit)。m、n、p 的位寬之和爲 6+12+4 = 22,小於 32,所以它們會挨着存儲,中間沒有縫隙。
如果將成員 m 的位寬改爲 22,那麼輸出結果將會是 8,因爲 22+12 = 34,大於 32,n 會從新的位置開始存儲,相對 m 的偏移量是 sizeof(unsigned int),也即 4 個字節。
(4.4)範例四
相鄰成員的類型不同:
int main(){
struct bs{
unsigned m: 12;
unsigned char ch: 4;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
return 0;
}
在 GCC 下的運行結果爲 4,三個成員挨着存儲;
在 VC/VS 下的運行結果爲 12,三個成員按照各自的類型存儲(與不指定位寬時的存儲方式相同)。
(4.5)範例五
如果成員之間穿插着非位域成員,那麼不會進行壓縮;
struct bs{
unsigned m: 12;
unsigned ch;
unsigned p: 4;
};
在各個編譯器下 sizeof 的結果都是 12。