C 语言位域

内容简介

位域用于在结构体中声明“这个成员占多少位”。它适合表达本地标志位、状态位和小范围整数,常见于嵌入式、协议描述、驱动和低层结构。

位域看起来很适合直接映射协议或寄存器,但它的实际布局受实现影响。跨平台二进制格式更推荐使用整数、掩码和移位显式编码。

用法

基本定义

typedef struct Flags {
    unsigned int readable : 1;
    unsigned int writable : 1;
    unsigned int executable : 1;
    unsigned int mode : 3;
} Flags;

成员后的 : 1: 3 表示占用的位数。

访问位域

Flags flags = {0};
flags.readable = 1;
flags.mode = 5;
 
if (flags.readable) {
    puts("readable");
}

位域成员像普通成员一样访问,但不能对位域成员取地址。

无名位域和对齐

typedef struct Control {
    unsigned int enabled : 1;
    unsigned int : 3;       // 跳过 3 位
    unsigned int mode : 4;
} Control;

无名位域可用于保留位或调整布局。宽度为 0 的无名位域可强制下一个位域进入新的存储单元,具体效果仍受实现影响。

用掩码替代位域

网络协议、文件格式、硬件寄存器更推荐显式掩码和移位:

#include <stdint.h>
 
uint8_t flags = 0;
flags |= 1u << 0;          // readable
flags |= 1u << 1;          // writable
 
if ((flags & (1u << 1)) != 0) {
    puts("writable");
}

这种写法更清楚地控制每一位的位置,不依赖编译器如何排列位域。

最佳代码实践

  • 位域适合本地内存中的紧凑标志,不适合默认作为跨平台 ABI。
  • 位域基础类型优先使用 unsigned int,避免有符号位域的实现差异。
  • 协议、文件、寄存器布局优先使用固定宽度整数和位运算。
  • 对保留位写 0,读取时用掩码忽略保留位。
  • 嵌入式寄存器场景如果使用位域,要结合编译器文档、ABI 和芯片手册验证。

错误用法

typedef struct Packet {
    unsigned int type : 3;
    unsigned int length : 13;
} Packet;
 
/* 错误倾向:假设 Packet 的位域布局就是网络协议布局 */
Flags flags;
unsigned int *p = &flags.readable;  // 错误:不能对位域成员取地址
typedef struct Bad {
    int state : 1;
} Bad;
 
/* 错误倾向:假设有符号 1 位字段一定能自然表示 0/1 */

注意事项

  • 位域成员排列顺序、是否跨存储单元、填充多少位,可能受编译器、目标平台和 ABI 影响。
  • 端序会影响多字节整数的二进制解释,也会影响你对位布局的直觉。
  • sizeof 可以用于包含位域的结构体,但不能用于单个位域成员。
  • volatile、位域和结构体常一起出现在寄存器映射代码里,但可移植性和读改写副作用要谨慎评估。

相关:C语言结构体C语言联合体C语言存储类型C语言类型转换规则

学习路径