C 位域

C 位域

如果程式的結構中包含多個開關量,只有 TRUE/FALSE 變數,如下:

struct
{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status;

這種結構需要 8 位元組的記憶體空間,但在實際上,在每個變數中,我們只儲存 0 或 1。在這種情況下,C 語言提供了一種更好的利用記憶體空間的方式。如果您在結構內使用這樣的變數,您可以定義變數的寬度來告訴編譯器,您將只使用這些位元組。例如,上面的結構可以重寫成:

struct
{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status;

現在,上面的結構中,status 變數將佔用 4 個位元組的記憶體空間,但是隻有 2 位被用來儲存值。如果您用了 32 個變數,每一個變數寬度為 1 位,那麼 status 結構將使用 4 個位元組,但只要您再多用一個變數,如果使用了 33 個變數,那麼它將分配記憶體的下一段來儲存第 33 個變數,這個時候就開始使用 8 個位元組。讓我們看看下面的例項來理解這個概念:

例項

#include <stdio.h> #include <string.h> /* 定義簡單的結構 */ struct { unsigned int widthValidated; unsigned int heightValidated; } status1; /* 定義位域結構 */ struct { unsigned int widthValidated : 1; unsigned int heightValidated : 1; } status2; int main( ) { printf( "Memory size occupied by status1 : %d\n", sizeof(status1)); printf( "Memory size occupied by status2 : %d\n", sizeof(status2)); return 0; }

當上面的程式碼被編譯和執行時,它會產生下列結果:

Memory size occupied by status1 : 8
Memory size occupied by status2 : 4

位域宣告

有些資訊在儲存時,並不需要佔用一個完整的位元組,而只需佔幾個或一個二進位制位。例如在存放一個開關量時,只有 0 和 1 兩種狀態,用 1 位二進位即可。為了節省儲存空間,並使處理簡便,C 語言又提供了一種資料結構,稱為"位域"或"位段"。

所謂"位域"是把一個位元組中的二進位劃分為幾個不同的區域,並說明每個區域的位數。每個域有一個域名,允許在程式中按域名進行操作。這樣就可以把幾個不同的物件用一個位元組的二進位制位域來表示。

典型的例項:

  • 用 1 位二進位存放一個開關量時,只有 0 和 1 兩種狀態。
  • 讀取外部檔案格式——可以讀取非標準的檔案格式。例如:9 位的整數。

位域的定義和位域變數的說明

位域定義與結構定義相仿,其形式為:

struct 位域結構名 
{

 位域列表

};

其中位域列表的形式為:

type [member_name] : width ;

下面是有關位域中變數元素的描述:

元素描述
type只能為 int(整型),unsigned int(無符號整型),signed int(有符號整型) 三種類型,決定了如何解釋位域的值。
member_name位域的名稱。
width位域中位的數量。寬度必須小於或等於指定型別的位寬度。

帶有預定義寬度的變數被稱為位域。位域可以儲存多於 1 位的數,例如,需要一個變數來儲存從 0 到 7 的值,您可以定義一個寬度為 3 位的位域,如下:

struct { unsigned int age : 3; } Age;

上面的結構定義指示 C 編譯器,age 變數將只使用 3 位來儲存這個值,如果您試圖使用超過 3 位,則無法完成。

struct bs{ int a:8; int b:2; int c:6; }data;

data 為 bs 變數,共佔兩個位元組。其中位域 a 佔 8 位,位域 b 佔 2 位,位域 c 佔 6 位。

讓我們再來看一個例項:

struct packed_struct { unsigned int f1:1; unsigned int f2:1; unsigned int f3:1; unsigned int f4:1; unsigned int type:4; unsigned int my_int:9; } pack;

在這裡,packed_struct 包含了 6 個成員:四個 1 位的識別符號 f1..f4、一個 4 位的 type 和一個 9 位的 my_int。

讓我們來看下面的例項:

例項

#include <stdio.h> #include <string.h> struct { unsigned int age : 3; } Age; int main( ) { Age.age = 4; printf( "Sizeof( Age ) : %d\n", sizeof(Age) ); printf( "Age.age : %d\n", Age.age ); Age.age = 7; printf( "Age.age : %d\n", Age.age ); Age.age = 8; // 二進位制表示為 1000 有四位,超出 printf( "Age.age : %d\n", Age.age ); return 0; }

當上面的程式碼被編譯時,它會帶有警告,當上面的程式碼被執行時,它會產生下列結果:

Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0

對於位域的定義尚有以下幾點說明:

  • 一個位域儲存在同一個位元組中,如一個位元組所剩空間不夠存放另一位域時,則會從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如:

    struct bs{ unsigned a:4; unsigned :4; /* 空域 */ unsigned b:4; /* 從下一單元開始存放 */ unsigned c:4 }

    在這個位域定義中,a 佔第一位元組的 4 位,後 4 位填 0 表示不使用,b 從第二位元組開始,佔用 4 位,c 佔用 4 位。

  • 位域的寬度不能超過它所依附的資料型別的長度,成員變數都是有型別的,這個型別限制了成員變數的最大長度,: 後面的數字不能超過這個長度。

  • 位域可以是無名位域,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:

    struct k{ int a:1; int :2; /* 該 2 位不能使用 */ int b:3; int c:2; };

從以上分析可以看出,位域在本質上就是一種結構型別,不過其成員是按二進位分配的。

位域的使用

位域的使用和結構成員的使用相同,其一般形式為:

位域變數名.位域名
位域變數名->位域名

位域允許用各種格式輸出。

請看下面的例項:

例項

int 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; /* 把位域變數 bit 的地址送給指標變數 pbit */ pbit->a=0; /* 用指標方式給位域 a 重新賦值,賦為 0 */ pbit->b&=3; /* 使用了複合的位運算子 "&=",相當於:pbit->b=pbit->b&3,位域 b 中原有值為 7,與 3 作按位與運算的結果為 3(111&011=011,十進位制值為 3) */ pbit->c|=1; /* 使用了複合位運算子"|=",相當於:pbit->c=pbit->c|1,其結果為 15 */ printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指標方式輸出了這三個域的值 */ }

上例程式中定義了位域結構 bs,三個位域為 a、b、c。說明了 bs 型別的變數 bit 和指向 bs 型別的指標變數 pbit。這表示位域也是可以使用指標的。