位域
位段(或稱“位域”丽蝎,Bit field)為一種 數(shù)據(jù)結(jié)構(gòu),可以把數(shù)據(jù)以 位元 的形式緊湊的儲存擎鸠,并允許程序員對此結(jié)構(gòu)的位元進(jìn)行操作揭鳞。
好處
- 可以使數(shù)據(jù)單元節(jié)省儲存空間炕贵,當(dāng)程序需要成千上萬個數(shù)據(jù)單元時,這種方法就顯得尤為重要汹桦。
- 段可以很方便的訪問一個 整數(shù) 值的部分內(nèi)容從而可以簡化程序源代碼鲁驶。
缺點(diǎn)
而位域這種數(shù)據(jù)結(jié)構(gòu)的缺點(diǎn)在于,其內(nèi)存分配與內(nèi)存對齊的實(shí)現(xiàn)方式依賴于具體的機(jī)器和系統(tǒng)舞骆,在不同的平臺可能有不同的結(jié)果钥弯,這導(dǎo)致了位段在本質(zhì)上是不可移植的。
C 中的位域
有些信息在存儲時督禽,并不需要占用一個完整的字節(jié)脆霎, 而只需占幾個或一個二進(jìn)制位。例如在存放一個開關(guān)量時狈惫,只有 0 和 1 兩種狀態(tài)睛蛛, 用一位二進(jìn)位即可鹦马。為了節(jié)省存儲空間,并使處理簡便忆肾,C 又提供了一種數(shù)據(jù)結(jié)構(gòu)荸频,稱為“位域”或“位段”。所謂“位域”是把一個字節(jié)中的二進(jìn)位劃分為幾個不同的區(qū)域客冈, 并說明每個區(qū)域的位數(shù)旭从。每個域有一個域名,允許在程序中按域名進(jìn)行操作场仲。 這樣就可以把幾個不同的對象用一個字節(jié)的二進(jìn)制位域來表示和悦。在聲明時,位段成員必須是整形或枚舉類型(通常是無符號類型)渠缕,且在成員名的后面是一個冒號和一個整數(shù)鸽素,整數(shù)規(guī)定了成員所占用的位元數(shù)。位域不能是靜態(tài)類型亦鳞。不能使用 &
對位域做取地址運(yùn)算馍忽,因此不存在位域的指針,編譯器通常不支持位域的引用(reference)蚜迅。以下程序則展示了一個位域的聲明:
例 1
struct p_t
{
int a:5;
int b:2;
int c:9;
} p;
對于變量 p 來說舵匾,p 有三個成員:a、b谁不、c,其中:a 占 5 位徽诲,b 占用 2 位刹帕,c 占用 9 位。那么 p 占用 16 位谎替,即 2 字節(jié)偷溺!錯~,總共占用 4 字節(jié)钱贯。不信挫掏?
struct p_t
{
int a:5;
int b:2;
int c:4;
} p;
int main()
{
printf("size of p is %lu\n", sizeof p);
return 0;
}
--- output
size of p is 4
不是說是按位存儲嗎?怎么會是 4 字節(jié)呢秩命?
從圖中我們可以看出尉共,a、b弃锐、c 共用了一個 int 的內(nèi)存空間袄友,一個 int 是 4 個字節(jié),所以不管你用沒用完霹菊,都至少是一個 int 內(nèi)存剧蚣。
例 2
struct p_t
{
unsigned char a:4;
unsigned char b:4;
} p;
int main()
{
printf("size of p is %lu\n", sizeof p);
return 0;
}
-- output
size of p is 1
兩個 char 占用 2 字節(jié),a 占用 4 位,b 占用 4 位鸠按,所以總共占用 1 字節(jié)礼搁。
例 3
struct p_t
{
unsigned char a:9;
unsigned char b:4;
} p;
有人可能會想到這樣聲明,但是該聲明不會通過編譯目尖,報錯 Width of bit-field 'a' (9 bits) exceeds width of its type (8 bits)
馒吴,因?yàn)?char 本身占用 1 字節(jié) 8 位,而聲明位域 9 位卑雁,明顯超過了 char 的空間募书,所以會報錯。
Tips
位域所占位數(shù)不能超過對應(yīng)的類型字節(jié)空間测蹲,比如:char 類型莹捡,位域不能超過 8 位;int 類型扣甲,位域不能超過 32 位篮赢。
例 4
struct p_t
{
unsigned char a:4;
unsigned char b:5;
} p;
--- output
size of p is 2
a 和 b 是相同類型的變量,但是 a琉挖、b 總的位數(shù)已經(jīng)超過 1 字節(jié)启泣,那么 b 就會存放到新的字節(jié)空間中,如圖:
例 5
struct p_t
{
unsigned char a:4;
unsigned short b:4;
} p;
--- output
size of p is 2
由于 a示辈、b 類型不同寥茫,在我的編輯器上,將 a 存儲到空間比較大的 b 上矾麻,共用 short 空間纱耻,也就是 2 字節(jié)。如果我們讓 b 是 13 位险耀,看看會發(fā)生什么弄喘?
struct p_t
{
unsigned char a:4;
unsigned short b:13;
} p;
--- output
size of p is 4
我們發(fā)現(xiàn)總共占用 4 字節(jié),為什么是 4 字節(jié)呢甩牺?a蘑志、b 總共 17 位,占用三個字節(jié)贬派,由于存在內(nèi)存對齊急但,需要額外的補(bǔ) 1 字節(jié)作為對齊(關(guān)于內(nèi)存對齊的知識參考前一篇文章)。
例 6
struct p_t
{
unsigned char a: 2;
unsigned char : 2;
unsigned char b: 4;
} p;
--- output
size of p is 1
在 a 與 b 之間有個無名的位域赠群,位數(shù)為 2羊始,那么意思就是說這兩位留空,b 存儲在這兩位之后查描,如圖:
例 7
struct p_t
{
unsigned char a: 2;
unsigned char : 0;
unsigned char b: 4;
} p;
--- output
size of p is 2
這次無名位域的值位 0突委,我們發(fā)現(xiàn)總的內(nèi)存占用變成了 2 字節(jié)柏卤,也就是說如過無名位域是 0 的話,那么會強(qiáng)制后續(xù)的位域在新的空間中分配匀油。注意:只有無名位域才可能為 0缘缚。
例 8
struct p_t
{
unsigned char a: 2;
unsigned char b;
unsigned char c: 4;
} p;
--- output
size of p is 3
如果中間穿插非位域成員,會強(qiáng)制后續(xù)字段在新的空間分配(前提是位域類型相同敌蚜,如果說不同的類型桥滨,不同的編譯器會有不同的優(yōu)化方式)。