石獅網(wǎng)站定制北京seo專業(yè)團(tuán)隊(duì)
🦄個(gè)人主頁(yè):小米里的大麥-CSDN博客
🎏所屬專欄:https://blog.csdn.net/huangcancan666/category_12718530.html
🎁代碼托管:黃燦燦 (huang-cancan-xbc) - Gitee.com
??操作環(huán)境:Visual Studio 2022
目錄
一、什么是位段?
二、位段的內(nèi)存分配?
三、位段的跨平臺(tái)問題
四、位段的應(yīng)用
五、代碼示例
六、位段的限制
七、位段與位域的區(qū)別
八、總結(jié)
共勉
一、什么是位段?
位段(Bit field)是一種數(shù)據(jù)結(jié)構(gòu),它允許你在單個(gè)整數(shù)變量中分配特定數(shù)量的位給不同的字段。這樣做的目的是為了節(jié)省內(nèi)存空間。
- 位段(Bit field)的基本單位不是字節(jié),而是位(bit)。位段是在單個(gè)整數(shù)類型變量中按照位來分配存儲(chǔ)空間的一種數(shù)據(jù)結(jié)構(gòu)。
- 位段的聲明和結(jié)構(gòu)是類似的,有兩個(gè)不同:
- 位段的成員必須是 int、unsigned int 或signed int 。
- 位段的成員名后邊有一個(gè)冒號(hào)和一個(gè)數(shù)字。
一個(gè)位段由多個(gè)成員組成,每個(gè)成員都有自己的名稱和寬度(即占用的位數(shù))。例如:
舉個(gè)例子,假設(shè)你需要存儲(chǔ)三個(gè)設(shè)置選項(xiàng),每個(gè)選項(xiàng)只需要一兩位來表示開啟或關(guān)閉的狀態(tài)。
如果用普通的整數(shù)變量來存儲(chǔ)這些選項(xiàng),每個(gè)整數(shù)至少會(huì)占用32位(如果是32位系統(tǒng)的話)。
但如果使用位段,你就可以只用三位來表示這三個(gè)選項(xiàng),大大節(jié)省了空間。struct Settings {unsigned int option1 : 1; // 占用1位unsigned int option2 : 1; // 占用1位unsigned int option3 : 1; // 占用1位
};
二、位段的內(nèi)存分配?
- 位段的成員可以是 int unsigned int signed int 或者是 char (屬于整形家族)類型
- 位段的空間上是按照需要以4個(gè)字節(jié)( int )或者1個(gè)字節(jié)( char )的方式來開辟的。
- 位段涉及很多不確定因素,位段是不跨平臺(tái)的,注重可移植的程序應(yīng)該避免使用位段。
Settings 就是一個(gè)位段類型,?那位段 Settings?的大小是多少?一起來看看:
請(qǐng)注意
雖然位段是以位為單位,但實(shí)際存儲(chǔ)這些位段的變量的大小通常是以字節(jié)為單位的。這是因?yàn)橛?jì)算機(jī)內(nèi)存是以字節(jié)為基本單元進(jìn)行尋址的。例如,即使你的位段結(jié)構(gòu)體只占用了8位(即1字節(jié)),由于內(nèi)存對(duì)齊的要求,實(shí)際的結(jié)構(gòu)體大小可能仍然是4字節(jié)。
總結(jié)一下:
- 位段的基本單位:位(bit)
- 位段存儲(chǔ)的單位:字節(jié)(byte),但位段本身按位分配空間
- 實(shí)際結(jié)構(gòu)體的大小:通常以字節(jié)為單位,取決于編譯器的內(nèi)存對(duì)齊策略。
再看看這個(gè):
#include <stdio.h> struct A {char _a : 3;char _b : 4;char _c : 5;char _d : 4; }; int main() {struct A a = {0};a._a = 10;a._b = 12;a._c = 3;a._d = 4;return 0; }
假設(shè):位段分配的內(nèi)存中的比特位是從右向左使用的,分配剩余的bit位不夠使用時(shí),浪費(fèi)掉剩余內(nèi)存。則:
執(zhí)行程序:
a._a = 10;
?10的二進(jìn)制為1010,放入_a中,由于_a只有3bit,需要截?cái)?#xff0c;所以舍棄最高位1,放入010:
執(zhí)行程序:
a._b = 12;
,12的二進(jìn)制為1100,剛好可以放入,如下圖:
執(zhí)行程序:
a._c = 3;
,3的二進(jìn)制為11,由于_c有5bit,高位添0,放入00011,如下圖:
執(zhí)行程序:
a._d = 4;
,4的二進(jìn)制為100,放入0100,如下圖:
程序就基本執(zhí)行完了,那么內(nèi)存中是什么樣的呢?根據(jù)上面分析,我們一開始給結(jié)構(gòu)體初始化為0,我們可以得到:
也就是:?
由于機(jī)器是小端存儲(chǔ),所以內(nèi)存上應(yīng)該是:62 03 04.
經(jīng)過調(diào)試,可以看到:
所以,位段的大小計(jì)算主要取決于你如何定義它以及編譯器的具體實(shí)現(xiàn)。
struct BitField {unsigned int a: 3; // 占用3位unsigned int b: 5; // 占用5位unsigned int c: 1; // 占用1位 }; 這里a占用了3位,b占用了5位,而c占用了1位。 理論上,這些位可以緊密排列在一起,但是實(shí)際的內(nèi)存對(duì)齊規(guī)則可能會(huì)導(dǎo)致額外的空間被分配。a、b和c總共占用了9位。 如果使用32位的整數(shù)類型,則最終的位段結(jié)構(gòu)可能會(huì)占用完整的32位,盡管實(shí)際上只使用了9位。所以,你可以使用sizeof運(yùn)算符來確定位段的實(shí)際大小: #include <stdio.h>int main() {struct BitField bitField;printf("Size of BitField: %zu bytes\n", sizeof(bitField));return 0; }
要計(jì)算位段的實(shí)際大小,你需要考慮以下幾點(diǎn):
- 位的總和:計(jì)算所有成員位數(shù)之和。
- 字邊界對(duì)齊:大多數(shù)編譯器會(huì)按照字邊界對(duì)齊原則來存儲(chǔ)數(shù)據(jù),這意味著即使位數(shù)總和小于一個(gè)字的基本單位(通常是8位或更常見的是32位),也會(huì)向上取整到下一個(gè)字的大小。
- 編譯器特定行為:不同的編譯器可能有不同的實(shí)現(xiàn)細(xì)節(jié),包括如何處理跨越字邊界的位字段。
三、位段的跨平臺(tái)問題
跟結(jié)構(gòu)相比,位段可以達(dá)到同樣的效果,但是可以很好的節(jié)省空間,但是有跨平臺(tái)的問題存在。簡(jiǎn)略一點(diǎn)看:
- int 位段被當(dāng)成有符號(hào)數(shù)還是無符號(hào)數(shù)是不確定的。
- 位段中最大位的數(shù)目不能確定。(16位機(jī)器最大16,32位機(jī)器最大32,寫成27,在16位機(jī)器會(huì)出問題。
- 位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配標(biāo)準(zhǔn)尚未定義。
- 當(dāng)一個(gè)結(jié)構(gòu)包含兩個(gè)位段,第二個(gè)位段成員比較大,無法容納于第一個(gè)位段剩余的位時(shí),是舍棄剩余的位還是利用,這是不確定的
詳細(xì)的看:
位字段的順序:在某些平臺(tái)上,位字段的順序可能會(huì)影響其布局。例如,在一些系統(tǒng)中,位字段是從低到高排列的(從右到左),而在其他系統(tǒng)中則可能是從高到低排列的(從左到右)。
位字段的對(duì)齊:編譯器可能會(huì)根據(jù)目標(biāo)平臺(tái)的內(nèi)存對(duì)齊要求來對(duì)位字段進(jìn)行對(duì)齊,這意味著位字段的實(shí)際布局可能會(huì)與預(yù)期不同。例如,在某些架構(gòu)上,整數(shù)類型可能需要在特定的地址對(duì)齊(如4字節(jié)邊界),這可能導(dǎo)致額外的空間被插入到位字段之間或之后。
位字段的大小:不同的編譯器可能會(huì)有不同的默認(rèn)整數(shù)類型大小。例如,
int
?類型在某些系統(tǒng)上可能是32位,在另一些系統(tǒng)上可能是64位。這會(huì)影響位字段的最大容量和布局。位字段的填充:為了滿足內(nèi)存對(duì)齊的要求,編譯器可能會(huì)在位字段之間添加填充位。例如,如果一個(gè)位字段在32位邊界結(jié)束,而下一個(gè)位字段需要從新的32位邊界開始,則編譯器可能會(huì)在它們之間插入未使用的位。
位字段的訪問:位字段的讀寫操作在不同的編譯器和平臺(tái)上可能會(huì)有所不同。有些編譯器提供特定的操作符來訪問位字段,而其他編譯器可能需要使用位操作(如位移和按位與操作)來訪問位字段。
為了避免位字段的跨平臺(tái)問題,你可以采取以下措施:
- 明確指定整數(shù)類型:使用?
<stdint.h>
?中定義的固定寬度整數(shù)類型(如?uint8_t
,?uint16_t
,?uint32_t
?等),以確保位字段的大小在所有平臺(tái)上都是一致的。- 手動(dòng)管理對(duì)齊:如果需要嚴(yán)格的對(duì)齊控制,可以考慮手動(dòng)在結(jié)構(gòu)體中添加填充字段。
- 避免依賴于位字段的特定布局:如果程序邏輯依賴于位字段的具體布局,那么在不同的平臺(tái)上測(cè)試并驗(yàn)證行為是很重要的。
- 使用位操作:使用位操作(如位移、按位與等)來訪問位字段,這樣可以確保代碼在不同平臺(tái)上的一致性。
四、位段的應(yīng)用
位段(Bit field)在計(jì)算機(jī)科學(xué)和軟件工程中有多種應(yīng)用,特別是在需要高效存儲(chǔ)和訪問數(shù)據(jù)的情況下。通常用于處理那些只需要少量位的數(shù)據(jù),比如狀態(tài)標(biāo)志、計(jì)數(shù)器等。通過使用位段,我們可以更有效地利用內(nèi)存資源。?
此外,位段還可以用來模擬位數(shù)組。例如,如果我們想要表示一個(gè)有 8 個(gè)元素的布爾數(shù)組,可以使用一個(gè)字節(jié)來存儲(chǔ)這個(gè)數(shù)組的所有元素:
struct {bool arr[8] : 1; // 每個(gè)元素占用1位
} bit_array;
這樣,我們就用一個(gè)字節(jié)的空間實(shí)現(xiàn)了布爾數(shù)組的功能。
五、代碼示例
下面是一些使用位段的例子:
// 示例1:定義一個(gè)表示顏色的位段
struct color {unsigned char red : 5; // 紅色部分占用5位unsigned char green : 6; // 綠色部分占用6位unsigned char blue : 5; // 藍(lán)色部分占用5位
};// 示例2:定義一個(gè)表示時(shí)間的位段
struct time {unsigned short hour : 5; // 小時(shí)部分占用5位unsigned short minute : 6; // 分鐘部分占用6位unsigned short second : 5; // 秒鐘部分占用5位
};
當(dāng)我們聲明一個(gè)位段結(jié)構(gòu)體變量時(shí),我們可以同時(shí)初始化所有成員的值。例如:
struct color my_color = { .red = 0x1f, .green = 0x3f, .blue = 0x1f }; // 初始化顏色位段變量
struct time my_time = { .hour = 12, .minute = 30, .second = 0 }; // 初始化時(shí)間位段變量
六、位段的限制
雖然位段可以節(jié)省內(nèi)存空間,但它也有一些限制:
- 位段不能包含浮點(diǎn)數(shù)成員。
- 位段不能包含字符串成員。
- 位段不能包含結(jié)構(gòu)體成員。
- 位段不能包含聯(lián)合體成員。
七、位段與位域的區(qū)別
位段和位域都是用來表示二進(jìn)制位的結(jié)構(gòu)體類型,但它們有一些區(qū)別:
- 位段可以包含多個(gè)成員,而位域只有一個(gè)成員。
- 位段的成員可以有不同的寬度,而位域的成員必須具有相同的寬度。
- 位段的成員可以跨越字邊界,而位域的成員不能跨越字邊界。
八、總結(jié)
位段是 C 語(yǔ)言提供的一種非常有用的工具,可以幫助我們更高效地管理內(nèi)存。但是,由于其跨平臺(tái)性的問題,我們?cè)谑褂梦欢螘r(shí)也需要謹(jǐn)慎考慮。