位域允许在结构体中以位为单位指定成员所占的宽度,常用于节省内存和直接映射硬件寄存器。
位操作是直接操控二进制位的运算,在嵌入式开发、标志位管理、协议解析中极为重要。通过宏封装可以实现可读性好的位操作接口。
| 语法 | 说明 |
|---|---|
unsigned int x : 4; |
位域,占 4 位 |
SET_BIT(x, n) |
将 x 的第 n 位置 1 |
CLEAR_BIT(x, n) |
将 x 的第 n 位置 0 |
TOGGLE_BIT(x, n) |
翻转 x 的第 n 位 |
GET_BIT(x, n) |
获取 x 的第 n 位值 |
SET_MASK(x, mask) |
按掩码置位 |
CLEAR_MASK(x, mask) |
按掩码清零 |
// 14_bitfield.c
#include <stdio.h>
// 网络协议头标志位
struct PacketFlags {
unsigned int syn : 1;
unsigned int ack : 1;
unsigned int fin : 1;
unsigned int rst : 1;
unsigned int version : 4;
unsigned int type : 8;
unsigned int length : 16;
};
// 硬件寄存器映射
struct GPIO_Reg {
unsigned int pin0 : 1;
unsigned int pin1 : 1;
unsigned int pin2 : 1;
unsigned int reserved : 13;
unsigned int mode : 4;
unsigned int irq_en : 1;
unsigned int pad : 11;
};
int main(void)
{
// 位域赋值
struct PacketFlags pkt = {0};
pkt.syn = 1;
pkt.version = 4;
pkt.type = 1;
pkt.length = 1500;
printf("Packet flags:\n");
printf(" syn=%u, ack=%u, fin=%u, rst=%u\n",
pkt.syn, pkt.ack, pkt.fin, pkt.rst);
printf(" version=%u, type=%u, length=%u\n",
pkt.version, pkt.type, pkt.length);
printf(" sizeof(struct PacketFlags) = %zu bytes\n", sizeof(struct PacketFlags));
// 位域不能取地址
// unsigned int *p = &pkt.syn; // 编译错误!
// 硬件寄存器操作
struct GPIO_Reg gpio = {0};
gpio.pin0 = 1;
gpio.pin2 = 1;
gpio.mode = 0xF;
gpio.irq_en = 1;
printf("\nGPIO register:\n");
printf(" pin0=%u, pin1=%u, pin2=%u\n", gpio.pin0, gpio.pin1, gpio.pin2);
printf(" mode=0x%X, irq_en=%u\n", gpio.mode, gpio.irq_en);
printf(" sizeof(struct GPIO_Reg) = %zu bytes\n", sizeof(struct GPIO_Reg));
return 0;
}
编译运行:
gcc -Wall -Wextra -std=c99 -o 14_bitfield 14_bitfield.c
./14_bitfield
// 14_bitops.c
#include <stdio.h>
#include <stdint.h>
// 位操作宏
#define SET_BIT(x, n) ((x) |= (1U << (n)))
#define CLEAR_BIT(x, n) ((x) &= ~(1U << (n)))
#define TOGGLE_BIT(x, n) ((x) ^= (1U << (n)))
#define GET_BIT(x, n) (((x) >> (n)) & 1U)
// 位掩码操作
#define SET_MASK(x, mask) ((x) |= (mask))
#define CLEAR_MASK(x, mask) ((x) &= ~(mask))
#define TOGGLE_MASK(x, mask)((x) ^= (mask))
#define CHECK_MASK(x, mask) (((x) & (mask)) == (mask))
// 权限标志定义
#define PERM_READ (1U << 0) // 0x01
#define PERM_WRITE (1U << 1) // 0x02
#define PERM_EXEC (1U << 2) // 0x04
#define PERM_DELETE (1U << 3) // 0x08
#define PERM_ADMIN (1U << 4) // 0x10
const char *perm_name(uint32_t perm)
{
static char buf[64];
buf[0] = '\0';
if (perm & PERM_READ) strcat(buf, "READ ");
if (perm & PERM_WRITE) strcat(buf, "WRITE ");
if (perm & PERM_EXEC) strcat(buf, "EXEC ");
if (perm & PERM_DELETE) strcat(buf, "DELETE ");
if (perm & PERM_ADMIN) strcat(buf, "ADMIN ");
return buf;
}
int main(void)
{
uint32_t perms = 0;
// 设置权限
SET_MASK(perms, PERM_READ | PERM_WRITE);
printf("After grant READ+WRITE: %s (0x%X)\n", perm_name(perms), perms);
// 添加执行权限
SET_BIT(perms, 2); // PERM_EXEC
printf("After grant EXEC: %s (0x%X)\n", perm_name(perms), perms);
// 检查是否有特定权限
printf("Has READ? %s\n", CHECK_MASK(perms, PERM_READ) ? "Yes" : "No");
printf("Has ADMIN? %s\n", CHECK_MASK(perms, PERM_ADMIN) ? "Yes" : "No");
// 撤销写权限
CLEAR_BIT(perms, 1);
printf("After revoke WRITE: %s (0x%X)\n", perm_name(perms), perms);
// 翻转删除权限
TOGGLE_MASK(perms, PERM_DELETE);
printf("After toggle DELETE: %s (0x%X)\n", perm_name(perms), perms);
TOGGLE_MASK(perms, PERM_DELETE);
printf("After toggle DELETE: %s (0x%X)\n", perm_name(perms), perms);
// 位域宽度限制
printf("\n--- Bitfield width demo ---\n");
uint8_t val = 0xFF;
printf("val=0x%02X, GET_BIT(val,3)=%u\n", val, GET_BIT(val, 3));
CLEAR_BIT(val, 7);
printf("After CLEAR_BIT(7): val=0x%02X\n", val);
return 0;
}
编译运行:
gcc -Wall -Wextra -std=c99 -o 14_bitops 14_bitops.c
./14_bitops
// 14_reg_ops.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// 模拟 32 位寄存器
#define REG32(addr) (*(volatile uint32_t *)(addr))
// 寄存器位操作宏(嵌入式风格)
#define REG_SET_BIT(reg, bit) ((reg) |= (1U << (bit)))
#define REG_CLEAR_BIT(reg, bit) ((reg) &= ~(1U << (bit)))
#define REG_READ_BIT(reg, bit) (((reg) >> (bit)) & 1U)
#define REG_WRITE_BITS(reg, pos, val, width) \
do { \
(reg) = ((reg) & ~(((1U << (width)) - 1) << (pos))) | \
(((val) & ((1U << (width)) - 1)) << (pos)); \
} while(0)
// 模拟硬件寄存器(实际中是内存映射地址)
static uint32_t mock_cr = 0; // 控制寄存器
static uint32_t mock_sr = 0; // 状态寄存器
// 控制寄存器位定义
#define CR_ENABLE 0
#define CR_RESET 1
#define CR_MODE_0 2
#define CR_MODE_1 3
#define CR_IRQ_EN 4
#define CR_DMA_EN 5
// 状态寄存器位定义
#define SR_BUSY 0
#define SR_DONE 1
#define SR_ERROR 2
#define SR_FIFO_LVL_0 8
#define SR_FIFO_LVL_1 9
#define SR_FIFO_LVL_2 10
int main(void)
{
printf("=== Embedded Register Bit Operations ===\n\n");
// 启用外设
REG_SET_BIT(mock_cr, CR_ENABLE);
REG_SET_BIT(mock_cr, CR_IRQ_EN);
REG_WRITE_BITS(mock_cr, CR_MODE_0, 0x3, 2); // mode = 11b
printf("CR after init: 0x%08X\n", mock_cr);
// 模拟状态
mock_sr |= (1 << SR_BUSY);
printf("SR (busy): 0x%08X\n", mock_sr);
// 读取 FIFO 水位
REG_WRITE_BITS(mock_sr, SR_FIFO_LVL_0, 5, 3);
uint32_t fifo = (mock_sr >> SR_FIFO_LVL_0) & 0x7;
printf("SR (fifo=5): 0x%08X, fifo_level=%u\n", mock_sr, fifo);
// 清除错误标志
REG_CLEAR_BIT(mock_cr, CR_ENABLE);
printf("CR (disabled): 0x%08X\n", mock_cr);
// 检查各个位
printf("\nBit dump of CR:\n");
for (int i = 0; i < 8; i++) {
printf(" bit %d = %u\n", i, REG_READ_BIT(mock_cr, i));
}
return 0;
}
编译运行:
gcc -Wall -Wextra -std=c99 -o 14_reg_ops 14_reg_ops.c
./14_reg_ops
位域不能取地址:&mystruct.bitfield 是编译错误,因为位域没有独立的地址。
位域的可移植性:
int 位域是有符号还是无符号由实现定义位域成员类型限制:只能是 int、unsigned int、_Bool、signed int 及其他实现定义的类型。不能是 float、char 数组等。
位操作宏要加括号:#define SET_BIT(x, n) ((x) |= (1U << (n))) — 不加括号会导致运算优先级错误。
1 vs 1U:1 << 31 在 32 位 int 上是未定义行为(符号位),用 1U << 31 更安全。
Q1:位域的存储规则是什么?
位域成员从低位到高位(或相反,取决于实现)打包到同一个存储单元(通常是
unsigned int)。不能跨存储单元分配,不够用时填充到下一个单元。
Q2:位域有什么可移植性问题?
- 位域从高位还是低位开始分配由编译器决定。
int位域是否有符号是实现定义的。- 不同平台上相同代码可能产生不同的内存布局。
Q3:SET_BIT 宏为什么要加双层括号?
防止宏展开后运算优先级出错。例如
SET_BIT(a | b, 3)如果不加括号会变成a | b |= 1 << 3,语义完全错误。
Q4:嵌入式中为什么要用位操作而不是位域?
- 位域的布局是实现定义的,不同编译器/平台可能不同。
- 位操作对所有编译器行为一致。
- 位操作可以处理
volatile寄存器。
Q5:如何用位掩码实现权限的添加、删除、检查?
添加:
perms |= PERM_READ;删除:perms &= ~PERM_READ;检查:(perms & PERM_READ) != 0。组合操作可以用SET_MASK、CLEAR_MASK、CHECK_MASK宏封装。