C語言位域解析及在嵌入式程式設計中的應用

2021-10-05 07:04:19 字數 4333 閱讀 2911

位域(或者也能稱之為位段,英文表達是 bit field)是一種資料結構,可以把資料以位元的形式緊湊的儲存,並允許程式設計師對此結構的位元進行操作。這種資料結構的好處是:

總體來說位域的定義可以分為兩大類,乙個是結構體位域,乙個是共用體體位域,由於共用體和結構體兩者在定義上的形式都是相同的,因此對於位域的定義從形式上看,兩者也都是相同的。

結構體位域定義的一般形式如下所示:

struct 位域結構體名

結構體變數名;

舉個簡單的例子進行說明:

struct example0

ex0_t;

上述定義是什麼意思呢,用一張圖就能很清楚地明白,下圖是所定義的結構體位域在記憶體中的儲存位置:

從圖中我們可以看出,雖然 x 的型別是 unsigned char ,但是並沒有佔 8 個位,而是佔了 3 個位,其取值範圍也變成了 0 ~ 2^3-1。

通過上述我們也可以猜到這個結構體位域的大小,筆者通過 printf 函式輸出結構體位域的大小為:

the value of sizeof

(ex0_t) is :

1 byte

共用體位域定義的一般形式跟結構體定義的一般形式是大致相同的,直接舉乙個簡單的例子進行說明:

union example1

ex1_u;

同樣的,筆者在這裡給出共用體位域在記憶體中的儲存位置:

這裡筆者也給出共用體位域的大小:

the value of sizeof

(ex1_u) is :

1 byte

由此也可以得出共用體位域大小遵循的原則是:共用體位域的總大小為最大基本型別成員的大小正如標題所示,在位域的使用過程中使用無符號的資料型別,下面給出乙個例子來說明這個例子:

struct bitfield_8

bf8;

bf8.a =

0x3;

/* 11 */

bf8.b =

0x5;

/* 101 */

printf

("%d,%d\n"

,bf8.a,bf8.b)

;

上述的輸出結果為:

-1,

-3

輸出結果並不是我們想要的,究其原因,實際上是因為 bf.a ,bf.b 都是有符號的,那麼自然也就有符號位的存在,而最高位為 1 代表負數,負數又是以補碼的形式儲存在計算機中的,所以也就有了上述的結果。

因此為了避免上述這種問題的出現,應該將 bitfield_8 中的 char 轉換成 unsigned char ,那輸出的結果就是 3,5

由於位域的特殊,同時也有了一些跟普通變數不同的特性:

struct bitfield_8

bf8;

printf

("%p\n"

,&bf8.a)

;/*錯誤*/

struct bitfield_8

bf8;

struct bitfield_8

bf8;

位域雖然能夠以位的形式運算元據,但是也被人們告知要慎重使用,原因就在於不同的處理器結構,不同的編譯器對於位域的一些特性會產生不同的結果,這也就是位域移植性差的原因

處理器對位域造成的影響也很容易理解,大端模式和小端模式的處理器會對下面的結構體位域產生不一樣的儲存方式,這裡比較簡單,如果對這個問題不清楚的朋友可以看筆者的這篇文章《union 的概念及在嵌入式程式設計中的應用》。

結構體位域成員不同型別

不同的編譯器對於位域會有不同的結果,比如下面這段**:

struct bitfield_5

bf_8;

intmain

(void

)

上述所定義的結構體位域中,對於結構體位域內成員不同資料型別,不同的編譯器有不同的處理,對於 visual studio 來說,面對不同的資料型別時,對於上述這個例子,儲存完第乙個成員 a 後,會重新另起 4 byte 的空間進行儲存,因此對於上述**在 visual studio 的執行結果是:

the value of sizeof

(bf_8) is 8 bytes

可見在 vs 環境下這樣使用位域不但沒有能夠節省記憶體空間,反而相比於結構體還擴大了。

上述是 vs 環境下的測試結果,下面是在 gcc 環境下的測試結果:

the value of sizeof

(bf_8) is 4 bytes

可見在 gcc 環境下,就算結構體位域成員的資料型別不一致,它其實按照「壓縮」資料的方式進行儲存的,也就是說結構體位域裡的成員都是挨著存放的。

成員大小之和超過乙個基本儲存空間

除了上述成員不同型別對於不同編譯器有不同的處理方式,當成員大小之和超過乙個基本儲存空間時,不同的編譯器也有不同的處理方式,比如下面這段**:

struct short_flag_t

;

對於上面這段**,同型別成員除了這樣定義之外,也可以這樣定義:

struct short_flag_t

;

上面的**因為 unsigned short 的大小是 2 個位元組,而成員 a,b加起來的大小已經超過了 2 個位元組,所以這種情況下也就有了以下兩種儲存方式:

不同編譯器可能面對這種情況會採用不同的儲存方式,對於 gcc 來說,採用的是第二種,如果編譯器採用的是第一種方式,而程式要求又需要按照第二種方式來進行儲存,又該如何辦呢?這時就要利用匿名 0 長度位域字段的語法強制位域在下乙個儲存單元儲存,示例**如下:

struct short_flag_t

上述**對於 a , b 來講,b 便不會緊挨著 a 進行儲存,而是強制使 b 在下乙個儲存單元進行儲存。

上述便是位域涉及的基本概念,那知道了基本概念之後,又能使用位域做些什麼呢?最容易另人想到的就是使用結構體位域定義標誌位,由於我們在裸機開發的過程中,沒有訊號量,事件等機制,通常會定義一些範圍只存在於 0~1 的開關量,而在沒有使用位域之前,最小的變數型別都是 1 個位元組,使用結構體位域將能夠根據取值範圍定義該變數的位數,從而起到節省記憶體的作用。

位域受到處理器和編譯器的影響,在使用前我們必須清楚當預處理器是大端對齊還是小端對齊,必須清楚當前編譯器對所定義的位域有何影響

如果我們現在要使用位域訪問乙個 8 位的暫存器,這個暫存器大致長這個樣子:

那麼我們就可以使用結構體位域構造這樣乙個資料結構:

typedef

union

bits;

}registertype;

registertype *preg =

(registertype *

)0x0000

8000

;

在進行了上述定義之後,我們就可以對暫存器進行操作了,首先,我們可以使用位域的方式操作暫存器的位,比如這樣:

preg->bits.bit5 =1;

preg->bits.bit012 =

7;

當然也可以利用 union 的特性直接操作整個暫存器,如下:

preg->byte =

0x55

;

使用位域完成對於暫存器的訪問,對於上述例子來講,我們必須要注意的一點是此例子是基於小端對齊模式的。

位域的用法雖然看起來更加靈活了,但是在使用時也要對我們的處理器和編譯器有所了解,如果為了寫出移植性較高的程式,應該避免使用位域。

嵌入式程式設計 c位操作

在學習c語言位操作前需要具備十六進製制和二進位制的知識以及從二進位制與十六進製制的相互轉換,相應的教程請移步新增鏈結描述 現在掌握了十六進製制和二進位制之間的相互轉換知識,我們可以從c中的按位 或位級別 運算開始。基本上有6種型別的按位運算子。這些是 1.以 表示 或 運算子 2.以 表示 與 運算...

嵌入式中C語言的位操作

位運算構建特定的二進位制數 技術公升級 使用巨集定義完成位運算 總結在操作中使用 將暫存器某些 特定位變成0,但是不影響其他位,可以進行如下操作,假設原來的暫存器reg1中的值為0xaaaaaaaa,希望將bit8 bit15清零並且其他位不進行改變,將這個數和0xffff00ff進行位與即可。re...

嵌入式C語言程式設計小知識

1.流水線被指令填滿時才能發揮最大效能,即每時鐘週期完成一條指令的執行 僅指單週期指令 如果程式發生跳轉,流水線會被清空,這將需要幾個時鐘才能使流水線再次填滿。因此,盡量少的使用跳轉指令可以提高程式執行效率,解決發案就是盡量使用指令的 條件執行 功能。2.在lpc2200系列中 可以通過過下面的程式...