什麼是位域
位域允許在結構體中指定成員佔用的精確位元數,用於:
- 節省記憶體 (如標誌位、狀態碼、硬體暖存器映射)
- 簡化位元操作 (免去手動遮罩/位移)
- 直接對應硬體/協定位元佈局
語法與基本範例
struct BitFlags {
unsigned int flag_a : 1; // 1 bit
unsigned int flag_b : 1;
unsigned int value : 6; // 6 bits
unsigned int : 0; // 強制下一成員從新儲存單元開始
unsigned int flag_c : 1;
};
關鍵規則
- 基礎類型:僅限
int、signed int、unsigned int (C99 允許 _Bool、其他實作定義類型)。
- 寬度限制:不得超過基礎類型位元寬度 (如
int 通常 32 位)。
- 儲存單元:位域盡量壓緊在同一個「儲存單元」 (通常等同基礎類型大小) 內;無法跨越單元邊界。
- 無名位域:
unsigned int : 3; 用於填充/對齊,不可存取。
- 零寬度位域:
unsigned int : 0; 強制後續位域從下一個儲存單元開始。
記憶體佈局實例分析
struct Demo {
int x : 8; // 第 0-7 位
int y : 2; // 第 8-9 位
int z : 6; // 第 10-15 位 (同一個 16-bit 單元)
int : 0; // 強制換單元
int w : 7; // 新單元第 0-6 位
};
// 假設 int 16 位:佔用 2 個 int (4 bytes)
// 假設 int 32 位:佔用 1 個 int (4 bytes,因未滿 32 位)
跨平台差異陷阱
| 特性 | 行為 | 可移植建議 |
| 位域分配順序 | LSB-to-MSB 或 MSB-to-LSB (實作定義) | 勿依賴跨平台二進制相容 |
| 儲存單元大小 | 通常等同 int,但非標準保證 | 使用固定寬度類型 uint32_t 做基礎 |
| 有號位域負值表示 | 補碼/一補碼/符號大小 (實作定義) | 位域優先用 unsigned |
| 結構體大小 | 受對齊要求影響 | 靜態斷言 _Static_assert(sizeof(struct)==N) |
實戰應用:硬體暖存器映射
// 某 MCU GPIO 控制暫存器 (32 bits)
union GPIO_REG {
uint32_t raw;
struct {
uint32_t pin0_out : 1;
uint32_t pin1_out : 1;
uint32_t : 2; // 保留
uint32_t pin0_mode : 2; // 00=IN, 01=OUT, 10=ALT
uint32_t pin1_mode : 2;
uint32_t : 24; // 保留
} bits;
};
void set_pin_mode(volatile union GPIO_REG *reg, int pin, int mode) {
if (pin == 0) reg->bits.pin0_mode = mode;
else if (pin == 1) reg->bits.pin1_mode = mode;
// 編譯器自動生成 read-modify-write 指令序列
}
位域 vs 手動位元操作
| 維度 | 位域 | 手動位元操作 |
| 可讀性 | 高 (語義清晰) | 低 (魔術數字) |
| 效能 | 編譯器優化,通常相當 | 完全可控 |
| 可移植性 | 低 (佈局實作定義) | 高 (明確控制) |
| 除錯支援 | 除錯器可直接顯示成員名 | 需手動解碼 |
總結建議
- ✅ 用於單一平台的硬體暖存器、檔案格式、網路協定標頭解析。
- ✅ 用於內部狀態旗標壓縮 (記憶體受限嵌入式系統)。
- ❌ 避免用於跨平台資料交換、序列化、檔案儲存格式。
- ❌ 避免在位域上取址 (
&struct.bitfield 非法)。