C語(yǔ)言位域解析及在嵌入式編程中的應(yīng)用
筆者能力有限,如文中出現(xiàn)錯(cuò)誤的地方,還請(qǐng)各位朋友能給我指出來(lái),我將不勝感激,謝謝~
位域的概念
位域(或者也能稱之為位段,英文表達(dá)是 Bit field)是一種數(shù)據(jù)結(jié)構(gòu),可以把數(shù)據(jù)以位元的形式緊湊的存儲(chǔ),并允許程序員對(duì)此結(jié)構(gòu)的位元進(jìn)行操作。這種數(shù)據(jù)結(jié)構(gòu)的好處是:
可以使數(shù)據(jù)單元節(jié)省存儲(chǔ)空間,當(dāng)程序需要成千上萬(wàn)個(gè)數(shù)據(jù)單元時(shí),這種數(shù)據(jù)結(jié)構(gòu)的優(yōu)點(diǎn)也就很明顯地突出出來(lái)了。
位段可以很方便地訪問(wèn)一個(gè)整數(shù)值的部分內(nèi)容從而簡(jiǎn)化程序源代碼。
位域的定義
總體來(lái)說(shuō)位域的定義可以分為兩大類,一個(gè)是結(jié)構(gòu)體位域,一個(gè)是共用體體位域,由于共用體和結(jié)構(gòu)體兩者在定義上的形式都是相同的,因此對(duì)于位域的定義從形式上看,兩者也都是相同的。
結(jié)構(gòu)體位域
結(jié)構(gòu)體位域定義的一般形式如下所示:
struct 位域結(jié)構(gòu)體名
{
類型說(shuō)明符 位域名 : 長(zhǎng)度;
}結(jié)構(gòu)體變量名;
舉個(gè)簡(jiǎn)單的例子進(jìn)行說(shuō)明:
struct example0
{
unsigned char x : 3;
unsigned char y : 2;
unsigned char z : 1;
}ex0_t;
上述定義是什么意思呢,用一張圖就能很清楚地明白,下圖是所定義的結(jié)構(gòu)體位域在內(nèi)存中的存儲(chǔ)位置:
從圖中我們可以看出,雖然 x 的類型是 unsigned char ,但是并沒(méi)有占 8 個(gè)位,而是占了 3 個(gè)位,其取值范圍也變成了 0 ~ 2^3-1。通過(guò)上述圖片我們也可以猜到這個(gè)結(jié)構(gòu)體位域的大小,筆者通過(guò) printf 函數(shù)輸出結(jié)構(gòu)體位域的大小為:
The Value of sizeof(ex0_t) is : 1 byte
關(guān)于結(jié)構(gòu)體位域的大小遵循這樣一個(gè)原則:整個(gè)結(jié)構(gòu)體位域的總大小為最寬基本類型成員大小的整數(shù)倍,這一原則與筆者在上一篇文章《結(jié)構(gòu)體內(nèi)存對(duì)齊解析》中寫的結(jié)構(gòu)體的總大小的原則是相同的。
共用體位域
共用體位域定義的一般形式跟結(jié)構(gòu)體定義的一般形式是大致相同的,直接舉一個(gè)簡(jiǎn)單的例子進(jìn)行說(shuō)明:
union example1
{
unsigned char x : 3;
unsigned char y : 2;
unsigned char z : 1;
}ex1_u;
同樣的,筆者在這里給出共用體位域在內(nèi)存中的存儲(chǔ)位置:
這里筆者也給出共用體位域的大?。?/p>
The Value of sizeof(ex1_u) is : 1 byte
由此也可以得出共用體位域大小遵循的原則是:共用體位域的總大小為最大基本類型成員的大小
結(jié)構(gòu)體位域詳解
位域的類型使用無(wú)符號(hào)型
正如標(biāo)題所示,在位域的使用過(guò)程中使用無(wú)符號(hào)的數(shù)據(jù)類型,下面給出一個(gè)例子來(lái)說(shuō)明這個(gè)例子:
struct BitField_8
{
char a : 2;
char b : 3;
}BF8;
BF8.a = 0x3;/* 11 */
BF8.b = 0x5;/* 101 */
printf("%d,%d\n",BF8.a,BF8.b);
上述的輸出結(jié)果為:
-1,-3
輸出結(jié)果并不是我們想要的,究其原因,實(shí)際上是因?yàn)?BF.a ,BF.b 都是有符號(hào)的,那么自然也就有符號(hào)位的存在,而最高位為 1 代表負(fù)數(shù),負(fù)數(shù)又是以補(bǔ)碼的形式存儲(chǔ)在計(jì)算機(jī)中的,所以也就有了上述的結(jié)果。因此為了避免上述這種問(wèn)題的出現(xiàn),應(yīng)該將 BitField_8 中的 char 轉(zhuǎn)換成 unsigned char ,那輸出的結(jié)果就是 3,5
位域禁止的操作
由于位域的特殊,同時(shí)也有了一些跟普通變量不同的特性:
結(jié)構(gòu)體位域成員不能夠使用取址操作
struct BitField_8
{
unsigned char a : 2;
}BF8;
printf("%p\n",&BF8.a); /*錯(cuò)誤*/
結(jié)構(gòu)體位域成員不能夠用 static 修飾
struct BitField_8
{
static unsigned char a : 2;/*錯(cuò)誤*/
}BF8;
結(jié)構(gòu)體位域成員不能夠使用數(shù)組
struct BitField_8
{
unsigned char a[5] : 5;/*錯(cuò)誤*/
}BF8;
不同處理器,不同編譯器對(duì)位域的影響
位域雖然能夠以位的形式操作數(shù)據(jù),但是也被人們告知要慎重使用,原因就在于不同的處理器結(jié)構(gòu),不同的編譯器對(duì)于位域的一些特性會(huì)產(chǎn)生不同的結(jié)果,這也就是位域移植性差的原因
處理器影響
處理器對(duì)位域造成的影響也很容易理解,大端模式和小端模式的處理器會(huì)對(duì)下面的結(jié)構(gòu)體位域產(chǎn)生不一樣的存儲(chǔ)方式,這里比較簡(jiǎn)單,如果對(duì)這個(gè)問(wèn)題不清楚的朋友可以看筆者的這篇文章《union 的概念及在嵌入式編程中的應(yīng)用》。
編譯器影響
結(jié)構(gòu)體位域成員不同類型
不同的編譯器對(duì)于位域會(huì)有不同的結(jié)果,比如下面這段代碼:
struct BitField_5
{
unsigned int a : 4;
unsigned char b : 4;
}BF_8;
int main(void)
{
printf("The Value of sizeof(BF_8) is:%lu bytes\n",sizeof(BF_8));
}
上述所定義的結(jié)構(gòu)體位域中,對(duì)于結(jié)構(gòu)體位域內(nèi)成員不同數(shù)據(jù)類型,不同的編譯器有不同的處理,對(duì)于 Visual Studio 來(lái)說(shuō),面對(duì)不同的數(shù)據(jù)類型時(shí),對(duì)于上述這個(gè)例子,存儲(chǔ)完第一個(gè)成員 a 后,會(huì)重新另起 4 byte 的空間進(jìn)行存儲(chǔ),因此對(duì)于上述代碼在 Visual Studio 的運(yùn)行結(jié)果是:
The Value of sizeof(BF_8) is 8 bytes
可見(jiàn)在 vs 環(huán)境下這樣使用位域不但沒(méi)有能夠節(jié)省內(nèi)存空間,反而相比于結(jié)構(gòu)體還擴(kuò)大了。上述是 VS 環(huán)境下的測(cè)試結(jié)果,下面是在 GCC 環(huán)境下的測(cè)試結(jié)果:
The Value of sizeof(BF_8) is 4 bytes
可見(jiàn)在 GCC 環(huán)境下,就算結(jié)構(gòu)體位域成員的數(shù)據(jù)類型不一致,它其實(shí)按照“壓縮”數(shù)據(jù)的方式進(jìn)行存儲(chǔ)的,也就是說(shuō)結(jié)構(gòu)體位域里的成員都是挨著存放的。
成員大小之和超過(guò)一個(gè)基本存儲(chǔ)空間
除了上述成員不同類型對(duì)于不同編譯器有不同的處理方式,當(dāng)成員大小之和超過(guò)一個(gè)基本存儲(chǔ)空間時(shí),不同的編譯器也有不同的處理方式,比如下面這段代碼:
struct short_flag_t
{
unsigned short a : 7;
unsigned short b : 10;
};
對(duì)于上面這段代碼,同類型成員除了這樣定義之外,也可以這樣定義:
struct short_flag_t
{
unsigned short a : 7,/*注意此處是逗號(hào)*/
b : 10;
};
上面的代碼因?yàn)?unsigned short 的大小是 2 個(gè)字節(jié),而成員 a,b加起來(lái)的大小已經(jīng)超過(guò)了 2 個(gè)字節(jié),所以這種情況下也就有了以下兩種存儲(chǔ)方式:
a , b 緊鄰
b 在下一個(gè)可存儲(chǔ)它的存儲(chǔ)單元內(nèi)分配內(nèi)存
不同編譯器可能面對(duì)這種情況會(huì)采用不同的存儲(chǔ)方式,對(duì)于 GCC 來(lái)說(shuō),采用的是第二種,如果編譯器采用的是第一種方式,而程序要求又需要按照第二種方式來(lái)進(jìn)行存儲(chǔ),又該如何辦呢?這時(shí)就要利用匿名 0 長(zhǎng)度位域字段的語(yǔ)法強(qiáng)制位域在下一個(gè)存儲(chǔ)單元存儲(chǔ),示例代碼如下:
struct short_flag_t
{
unsigned short a : 2;
unsigned short : 0;
unsigned short b : 3;
}
上述代碼對(duì)于 a , b 來(lái)講,b 便不會(huì)緊挨著 a 進(jìn)行存儲(chǔ),而是強(qiáng)制使 b 在下一個(gè)存儲(chǔ)單元進(jìn)行存儲(chǔ)。
位域的應(yīng)用
上述便是位域涉及的基本概念,那知道了基本概念之后,又能使用位域做些什么呢?最容易另人想到的就是使用結(jié)構(gòu)體位域定義標(biāo)志位,由于我們?cè)诼銠C(jī)開(kāi)發(fā)的過(guò)程中,沒(méi)有信號(hào)量,事件等機(jī)制,通常會(huì)定義一些范圍只存在于 0~1 的開(kāi)關(guān)量,而在沒(méi)有使用位域之前,最小的變量類型都是 1 個(gè)字節(jié),使用結(jié)構(gòu)體位域?qū)⒛軌蚋鶕?jù)取值范圍定義該變量的位數(shù),從而起到節(jié)省內(nèi)存的作用。
用于訪問(wèn)微控制器的寄存器
位域受到處理器和編譯器的影響,在使用前我們必須清楚當(dāng)前處理器是大端對(duì)齊還是小端對(duì)齊,必須清楚當(dāng)前編譯器對(duì)所定義的位域有何影響
如果我們現(xiàn)在要使用位域訪問(wèn)一個(gè) 8 位的寄存器,這個(gè)寄存器大致長(zhǎng)這個(gè)樣子:
那么我們就可以使用結(jié)構(gòu)體位域構(gòu)造這樣一個(gè)數(shù)據(jù)結(jié)構(gòu):
typedef union
{
unsigned char Byte;
struct
{
unsigned char bit012 : 3;
unsigned char bit34 : 2;
unsigned char bit5 : 1;
unsigned char bit6 : 1;
unsigned char bit7 : 1;
}bits;
}registerType;
現(xiàn)在假設(shè)我們這個(gè)寄存器的地址是 0x0000 8000,那么我們就可以定義一個(gè)指針并使其指向這個(gè)地址,如下:
registerType *pReg = (registerType *)0x0000 8000;
在進(jìn)行了上述定義之后,我們就可以對(duì)寄存器進(jìn)行操作了,首先,我們可以使用位域的方式操作寄存器的位,比如這樣:
pReg->bits.bit5 = 1;
pReg->bits.bit012 = 7;
當(dāng)然也可以利用 union 的特性直接操作整個(gè)寄存器,如下:
pReg->Byte = 0x55;
使用位域完成對(duì)于寄存器的訪問(wèn),對(duì)于上述例子來(lái)講,我們必須要注意的一點(diǎn)是此例子是基于小端對(duì)齊模式的。
總結(jié)
位域的用法雖然看起來(lái)更加靈活了,但是在使用時(shí)也要對(duì)我們的處理器和編譯器有所了解,如果為了寫出移植性較高的程序,應(yīng)該避免使用位域。
參考資料:
[1] https://aticleworld.com/access-the-port-and-register-using-bit-field-in-embedded-c/
[2] https://www.raviyp.com/bitfields-in-c-for-accessing-microcontroller-registers/
[3]https://aticleworld.com/bit-field-in-c/
您的閱讀是對(duì)我最大的鼓勵(lì),您的建議是對(duì)我最大的提升,歡迎點(diǎn)擊下方圖片進(jìn)入小程序進(jìn)行評(píng)論,或者添加筆者微信相互交流,微信二維碼在公眾號(hào)底部進(jìn)行獲取
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!