C语言 位域的使用
目錄
- 什么是位域
- 位域的定義
- 位域的使用
- 使用位域的注意點(重要)
- 實際應用
什么是位域
有些信息在存儲時,并不需要占用一個完整的字節,而只需占幾個或一個bit。例如在存放一個開關量時,只有 0 和 1 兩種狀態,用 1 bit即可。為了節省存儲空間,并使處理簡便,C 語言又提供了一種數據結構,稱為"位域"或"位段"。
所謂"位域"是把一個字節中的二進制位劃分為幾個不同的區域,并說明每個區域的位數。每個域有一個域名,允許在程序中按域名進行操作。這樣就可以把幾個不同的對象用一個字節的二進制位域來表示。
典型的應用場景:
- 用 1 位二進位存放一個開關量時,只有 0 和 1 兩種狀態。
- 讀取外部文件格式——可以讀取非標準的文件格式。例如:9 位的整數。
- 結構體中部分成員可能的賦值較小只需要用到1位或幾位,可以把這類成員湊到一起減少整個結構體占用的空間。
- 可巧妙用于位操作
位域的定義
位域定義與結構定義相仿,其形式為:
struct 位域結構名 {位域列表 };其中位域列表的形式為:
type [member_name] : width ;其中 type :類型說明符; member_name: 位域名; width :位域長度;
例如:
struct TESTA {char a:3;char b:5;char c:4;char d:4; };char 類型變量大小為一個字節(八位), 取值范圍為(-128, 127)。在上述位域的定義中,
a 只取三位,取值范圍為(-4, 3)。如果 a 為無符號型( unsigned char), 則取值范圍為(0, 7)。
位域的使用
位域的使用和結構成員的使用相同,其一般形式為:
位域變量名.位域名
或
位域變量指針名->位域名
位域允許用各種格式輸出。
示例:
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); /* 用指針方式輸出了這三個域的值 */ }結果:
使用位域的注意點(重要)
1、位域成員必須聲明為整型int、unsigned int或signed int類型,或是char,unsigned char,但不能是浮點型包括float,double
在ANSI C 中,這幾種數據類型是signed int和unsigned int;到了C99、C11新增了_Bool 的位字段。支持char一般是由于編譯器擴充的。
2、位域的長度不能超過它所依附的數據類型的長度,成員變量都是有類型的,這個類型限制了成員變量的最大長度,: 后面的數字不能超過這個長度。
例如:
這里就會報錯。
3、位域可以是無名位域,這時它只用來作填充或調整位置。無名的位域是不能使用的(沒有名稱當然沒法訪問)。例如:
struct k{int a:1;int :2; /* 該 2 位不能使用 */int b:3;int c:2; };4、當無名位域且長度度為0時,表示下一個變量從下一段地址開始存儲。(下一段地址的具體位置還得根據結構體的內存對齊原則)
示例:
結果:
上述例程中,位域a獨占一個字節,b和c共占一個字節。
b直接從下一個字節地址開始存儲了。
5、不能使用位域的地址,如:
struct k {char a : 2;char : 0;char b : 2;char c : 2; }; struct k test; printf("&test.a=%p\n",&test.a);//報錯 不能使用位域的地址6、當結構體位域成員超出了限定的位數,將發生上溢(溢出中的一種)。
#include <stdio.h>struct {unsigned int age : 3; } Age;int main() {unsigned int* ptr = &Age;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 有四位,超出了定義的3位長度printf("Age.age : %d\n", Age.age);printf("*p=%x\n", *ptr);return 0; }結果:
注意,從最后的*p=0可以看出,溢出沒有導致其他位發現改變,否則這里就會等于8了。
7、位域可以和正常的結構體成員寫到一起。如:
struct {unsigned int age1 : 3;unsigned int age2 : 3;unsigned int age3;unsigned int age4 : 3; } Age;8、一個位域存儲在N個字節中,如一個字節所剩空間不夠存放另一位域時,則會從下一單元起存放該位域。即不能跨字節存儲位域。
舉例:
sizeof(struct pack) = 8
一個unsigned int是4字節,a占了12位,還剩20字節,b需要24位,已經不夠了,又不能跨字節,因此b只能存在下一個unsigned int。
程序驗證:
結果:
由上面的測試可以驗證,a和b之間的確有空的區域,即結果中的00000部分;
9、整個結構體空間占用大小
含位域的結構體占用存儲大小規則大致如下:
第1,2點規則實際就是上面第8點所說的注意項問題,參考上文的示例即可。
第3點相鄰的位域字段的類型不同,則各編譯器的具體實現有差異舉例:
在Visual Studio 2019上運行結果:
在Linux上GCC編譯運行結果:
4
(gcc version:4.8.2)
第四點如果位域字段之間穿插著非位域字段,則不進行壓縮,指的是非位域字段不壓縮
#include <stdio.h>struct test {char a : 2;char b : 3;int d ;int c : 1; };int main(void) {printf("%d\n", sizeof(struct test));return 0; }如上述示例程序struct test的d就是插在位域中的非位域字段,它的類型是int,所以d占4個字節,a和b共占4個字節,c單獨占4個字節,因此sizeof(struct test) = 12
對于有位域的結構體占用存儲大小的問題,做一個精簡的方法總結:
帶有’位域’的結構體并不是按照每個域對齊的,而是將一些位域 成員’捆綁’在一起做對齊的。"捆綁"還需注意跨字節問題,不能跨字節存儲位域。位域捆綁后,按照普通結構體占用大小的規則計算即可。
實際應用
示例:
#include <stdio.h>union STATE {struct BITDATA{int D0 : 1;int D1 : 1;int D2 : 1;int D3 : 1;int D4 : 1;int D5 : 1;int D6 : 1;int D7 : 1;}BIT;int value; };int main(void) {int a = 0xFF;union STATE* sta;sta = &a;printf("sta.value=%02X\n",sta->value);printf("sizeof=%d\n", sizeof(sta->value));sta->BIT.D0 = 0;//給第一個位賦值printf("sta.value=%X\n", sta->value);printf("a=%X\n", a);return 0; }上述例程,使用union和位域實現對變量的某一bit位進行操作;該方式可用于計算機操作底層硬件寄存器。
結果:
參考:
https://blog.51cto.com/u_15244533/2845234
https://blog.51cto.com/u_14207158/2352294
https://blog.51cto.com/u_9233403/2121352
https://www.runoob.com/cprogramming/c-bit-fields.html
https://blog.csdn.net/sty124578/article/details/79456405
總結
- 上一篇: STM32 ADC采样使用内部参考电压
- 下一篇: MCU提高ADC采样精度的几种方案