在嵌入式软件开发中,结构体,联合体,以及位域是工程师们经常打交道的一组复合数据类型。下面用一个 32 位协议帧的例子,把它们串起来看。
| 特性 |
结构体 struct |
联合体 union |
位域 bit field |
| 内存 |
各成员依次排列,总大小 ≥ 各成员之和 |
所有成员共用同一块内存,大小 = 最大成员 |
在 struct/union 内部按 bit 分配 |
| 用途 |
组织不同类型数据 |
同一块内存多种解释方式 |
节省空间、硬件/协议精确控制 |
综合案例:模拟一个 32 位网络协议帧
场景
假设一个通信协议规定,一帧数据长 4 字节(32 bit),格式如下:
| 帧头(1bit) | 命令类型(3bit) | 地址(8bit) | 数据(16bit) | 保留(4bit) |
同时,这 4 个字节也可以整体当作一个 uint32_t 来发送/接收。
下面看代码:
#include <stdio.h>
#include <stdint.h>
/* 1️⃣ 结构体 + 位域:描述协议帧的位级布局 */
struct FrameBits {
uint32_t header : 1; // 帧头标志
uint32_t cmd : 3; // 命令类型 (0~7)
uint32_t addr : 8; // 目标地址
uint32_t data : 16; // 数据载荷
uint32_t reserved: 4; // 保留位
};
/* 2️⃣ 联合体 + 结构体 + 位域:同一块内存两种访问方式 */
union Frame {
uint32_t raw; // 整体 32 位原始值(用于发送/接收)
struct FrameBits bits; // 按位域访问(用于解析/构造)
};
int main(){
union Frame f;
/* 方式一:通过位域构造一帧数据 */
f.bits.header = 1;
f.bits.cmd = 3; // 写命令
f.bits.addr = 0x55; // 地址 0x55
f.bits.data = 0xCAFE;// 数据 0xCAFE
f.bits.reserved = 0;
/* 查看原始十六进制值(方便网络发送) */
printf("raw = 0x%08X\n", f.raw);
/* 方式二:从原始数据解析(模拟收到一帧) */
union Frame rx;
rx.raw = 0xA55F0031;
printf("\n--- 解析收到的帧 ---\n");
printf("header = %u\n", rx.bits.header);
printf("cmd = %u\n", rx.bits.cmd);
printf("addr = 0x%02X\n", rx.bits.addr);
printf("data = 0x%04X\n", rx.bits.data);
printf("reserved = %u\n", rx.bits.reserved);
return 0;
}
| 操作 |
发生了什么 |
f.bits.cmd = 3 |
只写入低字节中的 3 个 bit,不影响其他位 |
f.raw |
直接读出整个 32 位值,可以 send(socket, &f.raw, 4, 0) |
rx.raw = 0xA55F0031 |
模拟从网络/串口收到 4 字节 |
rx.bits.addr |
从同一块内存中按位提取地址字段,无需移位掩码 |
传统写法需要手动移位掩码:
addr = (raw >> 4) & 0xFF; // 繁琐易错
用位域后编译器自动搞定,代码可读性大幅提升。
再看一个嵌入式里更常见的 GPIO 寄存器例子:
/* GPIO 控制寄存器:32 bit */
union GPIO_Reg {
uint32_t all; // 整体操作
struct {
uint32_t mode : 2; // 00=输入 01=输出
uint32_t pullup : 1;
uint32_t out_val : 1;
uint32_t in_val : 1;
uint32_t : 27; // 保留
} bits;
};
#define GPIOA ((volatile union GPIO_Reg *)0x40010800)
/* 使用 */
GPIOA->bits.mode = 1; // 设为输出
GPIOA->bits.out_val = 1; // 输出高电平
它们的关系可以这样表示:
┌─────────────────────────────────────────┐
│ union(联合体) │ ← 同一内存多种解释
│ ┌───────────────────────────────────┐ │
│ │ struct(结构体) │ │ ← 组织多个字段
│ │ ┌───┐ ┌───┐ ┌──────┐ ┌────────┐ │ │
│ │ │bit│ │bit│ │ bit │ │ bit │ │ │ ← 位域精确控制
│ │ │ 1 │ │ 3 │ │ 8 │ │ 16 │ │ │
│ │ └───┘ └───┘ └──────┘ └────────┘ │ │
│ └───────────────────────────────────┘ │
│ uint32_t raw ← 原始 32 位值 │
└─────────────────────────────────────────┘
|