10.2.1 位域
位域 (Bit-field) 是结构体成员的一种声明形式,用来按“位宽”而不是按“完整类型宽度”分配存储。它适合表达硬件寄存器标志位、协议头部标记或紧凑状态位集合。
1. 基本语法
位域成员在结构体中声明为 类型 成员名 : 位宽,其中位宽必须是非负整数常量表达式。
c
struct flags {
unsigned int carry : 1;
unsigned int zero : 1;
unsigned int mode : 2;
};1
2
3
4
5
2
3
4
5
上例中 carry 和 zero 各占 1 位,mode 占 2 位。实际布局顺序、填充方式、跨存储单元策略由实现定义,因此跨平台二进制协议不能直接依赖位域内存布局。
2. 常见约束
位域只能作为结构体或联合体成员声明,不能单独定义为普通对象。对位域不能取地址,也不能把它当作普通对象指针目标使用。
c
struct status {
unsigned int ready : 1;
};
/* &s.ready 是不合法的 */1
2
3
4
5
2
3
4
5
此外,类型为 int 的位域其有符号性可能由实现决定;若需要确定语义,建议显式写 signed int 或 unsigned int。
3. 宽度为 0 的匿名位域
匿名位域可用于占位或控制下一个成员的对齐边界,其中“宽度为 0”表示强制从下一个存储单元开始放置后续位域。
c
struct header {
unsigned int a : 3;
unsigned int : 0; /* 强制换到下一个存储单元 */
unsigned int b : 3;
};1
2
3
4
5
2
3
4
5
这种写法在与硬件文档对齐时很有用,但仍然要结合目标编译器文档验证布局结果。
4. 使用建议
位域适合“同一编译器、同一 ABI、同一目标平台”的内部表示;若数据需要跨平台序列化,建议改用显式掩码与移位操作,对字节序与位序进行手动控制,这样更符合可移植性要求。
5. 并发访问提示
位域写入常常对应底层存储单元的读改写过程,而不是独立位的原子更新。若多个线程并发修改同一存储单元中的不同位域,仍可能产生竞态。涉及并发读写时,应配合同步原语或改用显式原子对象方案。
6. 位宽越界与赋值截断
给位域赋值时,若值超出可表示范围,结果依实现而定,代码可读性也会显著下降。更清晰的写法是先在普通无符号整数对象里做范围约束,再写入位域,这样意图和边界都更明确。
c
struct reg_bits {
unsigned int mode : 2;
};
void set_mode(struct reg_bits *r, unsigned int mode) {
r->mode = mode & 0x3u;
}1
2
3
4
5
6
7
2
3
4
5
6
7
习题
#11006
⚡4⏳3
一个文本界面使用下面的位域结构描述显示样式:
c
#include <stdbool.h>
#include <stdint.h>
enum {
TEXT_ALIGN_LEFT = 0,
TEXT_ALIGN_CENTER = 1,
TEXT_ALIGN_RIGHT = 2
};
typedef struct {
unsigned int font_id : 8;
unsigned int size : 7;
unsigned int alignment : 2;
unsigned int bold : 1;
unsigned int italic : 1;
unsigned int underline : 1;
} TextStyle;
bool text_style_encode(
const TextStyle *style,
uint32_t *out_code
);
bool text_style_decode(
uint32_t code,
TextStyle *out_style
);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
设计一个与位域实际内存布局无关的 32 位交换格式:
- 第 0~7 位:
font_id; - 第 8~14 位:
size; - 第 15~16 位:
alignment; - 第 17、18、19 位:依次表示
bold、italic、underline; - 第 20~31 位:保留,必须为 0。
要求:
size的合法范围是 1~127,alignment只能取三个已定义值。- 编码或解码失败时返回
false,并且不得修改输出对象。 - 解码时,保留位非零、字号为 0 或对齐方式非法都视为失败。
- 只能使用无符号整数上的掩码与移位构造交换格式;不得通过联合体、指针转换或
memcpy复制位域对象表示。 - 对每个合法样式,先编码再解码应恢复所有字段的值。
- 说明为什么不能用
sizeof(TextStyle) == sizeof(uint32_t)作为该交换格式可移植的依据。