有些信息在存儲時,並不需要佔用一個完整的字節, 而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有0和1 兩種狀態,用一位二進位即可。爲了節省存儲空間,並使處理簡便,C語言提供了一種數據結構,稱爲“位域”或“位段”。所謂“位域”是把一個類型單元中的二進位劃分爲幾個不同的區域, 並說明每個區域的位數。每個域有一個域名,允許在程序中按域名進行操作。 這樣就可以達到壓縮數據的目的。
位域定義
位域定義與結構定義相仿,其形式爲:
struct slabel { 位域類型 位域名:位域bit長度; 位域類型 :位域bit長度; … }; |
在K&R中,規定的位域類型只能是ints——這應該是包括signed和unsigned的char、short和int。而且最好註明是signed還是unsigned類型的。誠然,C99中增加了新的支持類型,不過爲了兼容性,還是遵循以前的標準吧。據我所知,也就是Borland、GCC的最新版本才能比較完善地支持C99,而Microsoft似乎並不支持,而且看上去將來也不會打算支持了。順便發句牢騷,C99出來後C和C++似乎分道揚鑣了,不爽啊。希望C++的新標準能讓它們貼近點吧。
實際上,我覺得位域可以看作是取值範圍縮小了的位域類型:所有對位域類型能做的操作,對位域都能做。如對int型的位域,可以用printf(“%d”,位域名)來輸出,也可以用scanf(“%d”,&位域名)來進行輸入。
位域的壓縮與對齊
位域的內存對齊有其特殊的地方,因爲在對齊的過程中不僅穿插着數據的壓縮,而且對齊也分爲字段對齊和位域類型對齊兩種。而這裏的位域類型對齊實際上就是一般的數據內存對齊。具體的壓縮與對齊規則如下:
1) 壓縮:如果相鄰位域字段的位域類型相同,各個位域字段只佔定義時的bit長度。
struct test1 { char a : 2; char b : 3; char c : 1; }; 其大小爲1 bytes,a和b和c在一個char中。 |
2) 壓縮和字段對齊:一個位域字段必須存儲在其位域類型的一個單元所佔空間中,不能橫跨兩個該位域類型的單元。也就是說,當某個位域字段正處於兩個該位域類型的單元中間時,只使用第二個單元,第一個單元剩餘的bit位置補(pad)0。
struct test2 { char a : 2; char b : 3; char c : 7; }; 其大小爲2 bytes,a和b在一個char中,c在第二個char中。 |
3) 位域類型對齊:如果相鄰的位域字段的類型不同,在不同的位域類型間,按通用的對齊規則進行不同數據類型間的對齊(注意,struct的長度是其內部最寬類型的整數倍);同時在相同位域類型的字段間按以上兩條規則對齊。
struct test3 { char a:1; char :2; long b:3; char c:2; }; 其大小爲12bytes。 |
4) 終止壓縮:如果位域字段之間穿插着非位域字段,則不進行壓縮;
struct test3 { char a:1; char :2; long b; char c:2; }; 其大小爲12bytes,如果註釋掉long b,則爲1byte。 |
順便指出,沒有位域名的位域字段是不能使用的,只是用來佔位置。
問題和總結
1. 網上有一種說法是位域的長度不能跨字節,這是完全錯誤的。在壓縮與對齊規則的第二條中說明了正確的規定。其實K&R中的原話是“is a set of adjacent bits within a single implementation-defined storage unit that we will call a ``word.''“。首先,word不是字節的意思,而這個word是不是兩個字節,我看也不是。這裏的word指的應該是同一種位域類型在不同系統中不同的實現長度。正因爲如此,作者才用了implementation-defined這個詞。以下是證明可以跨字節的例程:
#include <stdio.h> int main() { struct foo4 { char a : 2; char b : 3; int c : 18; }; struct foo4 t; t.c = 50*1024; printf("%d",t.c); int len = sizeof(foo4); printf("%d",len); return 0; } 輸出t.c是51200,可以看出c可以取到18bits。 |
2. 因爲在一個byte中,bit位也是有分big-endian和small-endian的,所以位域的移植性並不好。不過在嵌入式中由於有許多特定的設備,在此中可以盡情使用位域。
在本文中,位域類型就是數據類型,呵呵。位域可以看作是取值範圍縮小了的位域類型,所有對位域類型能做的操作,對位域都能做。比較難的地方在於位域字段的壓縮與對齊。嗯,還有signed和unsigned類型要特別注意。