66.嵌入式C语言进阶:共用体(Union)实战指南——单片机内存省一半的神级技巧

一、共用体是什么?和结构体有什么区别?

很多人会把共用体和结构体混淆,其实它们的核心差异在于内存分配方式:

  • 结构体(struct):每个成员单独分配独立内存,总大小是所有成员大小之和。
  • 共用体(union):所有成员共享同一块内存空间,同一时间只能有效保存其中一个成员的数据。

举个形象的例子:结构体就像一间被隔成多个小房间的公寓,每个房间住不同的人;共用体则是一间多功能房,白天当办公室,晚上当卧室,同一时间只能做一件事。

二、共用体的3个核心特点

  1. 内存大小等于最大成员的大小:比如一个共用体里有int(4字节)和char buf4(4字节),那这个共用体的大小就是4字节,刚好能容纳最大的成员。
  2. 成员互斥,修改一个覆盖其他:给共用体的一个成员赋值后,其他成员的值会被覆盖,因为它们共享同一块内存。
  3. 适合数据类型转换与拆分:可以把一个整数拆成字节数组,也可以把字节数组拼成整数,在串口协议解析、传感器数据处理中特别好用。

三、基础代码示例:理解内存共享

我们先看一个最基础的共用体示例,直观感受它的内存共享特性:

c 复制代码
#include <stdio.h>

// 定义共用体:int和char buf[4]共享4字节内存
union Data
{
    unsigned int num;    // 4字节
    unsigned char buf[4]; // 4字节
};

int main(void)
{
    union Data val;
    val.num = 0x12345678; // 给int成员赋值
    printf("第0字节: 0x%02X\n", val.buf[0]);
    printf("第1字节: 0x%02X\n", val.buf[1]);
    return 0;
}

运行结果

text 复制代码
第0字节: 0x78
第1字节: 0x56

这说明在小端模式的单片机中,整数的低字节存在低地址,高字节存在高地址,共用体直接帮我们完成了整数到字节的拆分。

四、嵌入式实战场景:串口协议解析

在单片机开发中,串口通信经常需要把整数拆成字节发送,或者把接收到的字节拼成整数,用共用体可以避免手动位运算,代码更简洁高效。

场景1:整数拆分成字节发送

c 复制代码
#include <stdio.h>
#include <string.h>

// 定义共用体:int和字节数组共享内存
typedef union {
    unsigned int data;
    unsigned char bytes[4];
} IntToBytes;

int main()
{
    IntToBytes converter;
    converter.data = 0xAABBCCDD;
    
    // 直接通过bytes成员获取拆分后的字节
    printf("拆分后的字节:\n");
    for(int i=0; i<4; i++){
        printf("字节%d: 0x%02X\n", i, converter.bytes[i]);
    }
    
    // 模拟串口发送:依次发送converter.bytes[0]到converter.bytes[3]
    return 0;
}

场景2:字节拼成整数接收

c 复制代码
#include <stdio.h>
#include <string.h>

typedef union {
    unsigned int data;
    unsigned char bytes[4];
} BytesToInt;

int main()
{
    BytesToInt converter;
    // 模拟接收到的字节流
    unsigned char recv_buf[4] = {0xDD, 0xCC, 0xBB, 0xAA};
    
    // 把接收到的字节复制到共用体的bytes成员
    memcpy(converter.bytes, recv_buf, 4);
    // 直接通过data成员获取拼接后的整数
    printf("拼接后的整数:0x%08X\n", converter.data);
    
    return 0;
}

五、进阶技巧:共用体+结构体嵌套操作寄存器

在单片机中,我们经常需要操作硬件寄存器,既要能整体读写寄存器,又要能单独修改某一个位,这时候用共用体+结构体嵌套的方式会非常方便:

c 复制代码
#include <stdio.h>

// 定义结构体:拆分寄存器的各个位
typedef struct {
    unsigned char bit0 : 1;  // 位0
    unsigned char bit1 : 1;  // 位1
    unsigned char bit2 : 1;  // 位2
    unsigned char bit3 : 1;  // 位3
    unsigned char reserved : 4; // 保留位
} RegBits;

// 定义共用体:整体寄存器和位结构体共享内存
typedef union {
    unsigned char reg_val; // 整体寄存器值
    RegBits bits;          // 拆分后的位
} Register;

int main()
{
    Register reg;
    reg.reg_val = 0x03; // 整体赋值
    
    // 单独修改位2,不影响其他位
    reg.bits.bit2 = 1;
    printf("修改后的寄存器值:0x%02X\n", reg.reg_val);
    
    return 0;
}

六、使用共用体的注意事项

  1. 不要同时读取多个成员:因为成员共享内存,同时读取会得到错误的值,要确保当前只有一个成员是有效的。
  2. 注意字节序问题:不同单片机可能是大端或小端模式,跨设备通信时要统一字节序。
  3. 内存对齐:共用体的大小会对齐到最大成员的对齐数的整数倍,比如如果有double成员,大小会对齐到8字节。

七、总结

共用体是嵌入式C语言中非常实用的技巧,它能帮你在内存有限的单片机中节省RAM,同时简化数据类型转换和协议解析的代码。在串口通信、传感器数据处理、寄存器操作等场景中,合理使用共用体能让你的代码更简洁、更高效。

如果你觉得这篇文章对你有帮助,欢迎点赞收藏,也可以在评论区分享你使用共用体的实战经验!

相关推荐
怪奇云呼军1 小时前
闪电智能 Voice Agent 怎样根据语速、停顿和追问方式切换话术?策略引擎拆解
开发语言·人工智能·网络协议·算法·语音识别·web app
Ivanqhz1 小时前
Rust parse() 浅析
开发语言·后端·rust
神仙别闹1 小时前
基于C++实现(控制台)景区旅游管理系统
开发语言·c++·旅游
ACP广源盛139246256733 小时前
此芯 P1 AI BOX / AI NAS@ACP#IX8008、IX8024 应用场景与市场机会
大数据·人工智能·分布式·单片机·嵌入式硬件
牛奔10 小时前
Go 如何打印调试深层或嵌套的结构体
开发语言·后端·golang
广药门徒10 小时前
虚拟机联网20260725
嵌入式硬件
2601_9516443210 小时前
C语言二级备考指南
c语言·编程基础·编译环境·二级考试·题库选择
geovindu10 小时前
go: Iterative Algorithms
开发语言·后端·算法·golang·迭代算法
学逆向的10 小时前
汇编——JCC指令
开发语言·汇编·网络安全