找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4598

积分

0

好友

598

主题
发表于 1 小时前 | 查看: 4| 回复: 0

在嵌入式软件开发中,结构体,联合体,以及位域是工程师们经常打交道的一组复合数据类型。下面用一个 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 位值            │
└─────────────────────────────────────────┘



上一篇:CozyClay:开源浏览器3D预演工具,npx一条命令搞定AI视频分镜
下一篇:STM32单片机C语言数组越界为何不报错?从汇编到MPU深度解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-31 06:45 , Processed in 1.142154 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表