头图

在实际应用中联合体union的妙用

  关键字union,又称为联合体、共用体,联合体的声明和结构体类似,但是它的行为方式又和结构体不同,这里的行为方式主要指的是其在内存中的体现,结构体中的成员每一个占据不同的内存空间,而联合体中的所有成员共用的是内存中相同的位置。

  简单看下区别:

struct MyStruct 
{
    double a;
    int b;
    char c;
};
struct MyStruct value;
union MyUnion 
{
    double a;
    int b;
    char c;
};
union MyUnion value;

  同样是定义变量value;内存空间占用情况如下:
image.png

  可以看出,结构体变量中3个成员相当于3个人,每个人必须要住一间屋子,优点是空间包容性强,但是内存空间必须全部分配,不管房子住不住人。联合体变量3个成员,它们可以共用一间屋子,但是每个屋子同一时间只能容纳一个成员,因此不够包容,成员是互斥的,但是可以大大节省内存空间。

  要注意的是,联合体的长度大小为最大的成员的大小,在本例中即value.a的大小。并不是单指数据类型,若在MyUnion定义了数组char c[10],则此时该联合体变量value大小为10个字节。

  以上简单的了解了下union的基本定义,在实际应用中我们一般都使用结构体来定义数据组合而成的结构型变量,而在各数据类型各变量占用空间差不多并且对各变量同时使用要求不高的场合(单从内存使用上)也可以灵活的使用union。

1、变量的初始化

  在初始化的时候,只应对一个成员进行初始化即在初始化列表中只有一个初始值。原因就是联合体的所有成员共用一个首地址,在默认情况下,会将这个初始值初始化给联合体变量的第一个成员。

union MyUnion 
{
    double a;
    int b;
    char c;
};
//为第一个成员初始化
union MyUnion un1 = {5.0f};
//错误初始化,不能为多个成员初始化
union MyUnion un1 = {5.0f, 10};
//对其它位置的成员进行初始化,则可以通过指定初始化方式
union MyUnion un1 = {.b = 10};
//与结构体一样,也可以将一个联合体变量作为初始值,直接初始化给同类型的另一个联合体变量
union MyUnion un2 = un1;

2、数据位操作

#include<stdio.h>
typedef struct
{
  unsigned char bit0:1;
  unsigned char bit1:1;
  unsigned char bit2:1;
  unsigned char bit3:1;
  unsigned char bit4:1;
  unsigned char bit5:1;
  unsigned char bit6:1;
  unsigned char bit7:1;
}bitValue;
 
typedef union
{
  unsigned char bytedata;
  bitValue  bitdata; 
}regValue;
 
int main()
{
  regValue data;
  data.bytedata= 0x5A;
  printf("%d",data.bitdata.bit5);  //读取第6位
  data.bitdata.bit7 = 1;           //修改第8位
  return 0;
}

  可以看出,通过访问和修改联合体中的定义bitdata成员,可以间接的访问和修改定义的bytedata的值,这可以用在嵌入式的寄存器位操作上。

3、和struct嵌套使用

  比如我们分别定义电视和空调的属性:

struct tvFeature    //电视属性
{
   char *logo;     //品牌
   int price;      //价格
   int screensize  //屏幕尺寸  
   int resolution  //分辨率 
}tvFeature;
struct tvFeature tvfeature;
 
struct airFeature  //空调属性
{
   char *logo; //品牌
   int price;   //价格
   int coldcapacity;//制冷量 
   int hotcapacity;//制热量
}airFeature;
struct airFeature airfeature;

  可以看出电视和空调有相同的属性,也有各自特有的属性。我们可以使用家用电器的数据结构统一定义。但是这样用统一的数据结构,定义电视和空调的变量之间耦合会增加很多,对于tvfeature和airfeature各自来说用不到的属性也会浪费内存空间。

struct homeappliancesFeature  //电器属性
{
   char *logo; //品牌
   int price;   //价格
   int screensize  //屏幕尺寸  
   int resolution  //分辨率
   int coldcapacity;//制冷量 
   int hotcapacity;//制热量
}homeappliancesFeature;
 
struct homeappliancesFeature tvfeature;
struct homeappliancesFeature airfeature;

  因此可以用union来解决问题:

struct tvFeature    //电视属性
{
   int screensize  //屏幕尺寸  
   int resolution  //分辨率 
}tvFeature;
struct airFeature  //空调属性
{
   int coldcapacity;//制冷量 
   int hotcapacity;//制热量
}airFeature;
 
struct homeappliancesFeature  //电器属性
{
   char *logo; //品牌
   long country; //国家
   union
   {
      struct tvFeature tvST;
      struct airFeature airST;
   };
};
struct homeappliancesFeature tvfeature;
struct homeappliancesFeature airfeature;

  如上我们只需一个结构体,就可以解决电视和空调的属性不同问题;struct tvFeature tvST和struct airFeature airST共用一块内存空间,定义变量时,可以访问各自的特有属性,这样就解决了内存浪费和变量耦合高的问题。

4、数据复制

  例如串口数据发送时,可以直接使用数据复制的方式将数据打包发送,不需要将一个4字节的数据额外进行拆分为4个单字节的数据;反之读取数据时,也可以不用将4个单字节的数据重新通过移位拼接为一个4字节数据。

typedef union
{
  uint8   data8[4];
  uint32  data32;
}dataType;
 
uint32 sendData = 0x5A5AA5A5;
uint32 receiveData;
dataType commSend;
void main(void)
{
    uint8 commData[128];     
    //数据复制
    commData.data32 = sendData;    
    //发送数据,字节复制,不需要再将commData.data32单独移位拆分
    commData[0]= commSend.data8[0];
    commData[1]= commSend.data8[1];
    commData[2]= commSend.data8[2];
    commData[3]= commSend.data8[3];
      
    //读取数据时,字节复制,不需要再将已经读取到的4个单字节数据拼接 
    receiveData =  commData.data32;  
}

5、分时发送不同帧格式数据

  比如需要在同一段通信数据发送逻辑中,针对不同通信协议帧格式进行发送时,就可以这样定义数据结构。

typedef struct 
{ 
   uint8 head;   //帧头格式相同
   union    //中间数据格式不一样
   {
      struct             //payloadType1  
      {
        uint8 cmd;
        uint8 type;
        uint8 data[5];   
        uint8 check;       
      }msgType1;
   
      struct              //payloadType2    
      {
        uint16 cmd;     
        uint8 data[8];   
        uint16 check;       
      }msgType2;  
          
     uint8 data[10];      //payloadType3  
   } payloadType;
   uint8 end;    //帧尾格式相同
}frameType;

  By the way:在使用联合体时可以注意这两个点:

1、数据大小端

  使用联合体时需要注意数据大小端问题,这个取决于实际的处理器的存储方式。
  大端存储就是高字节数据放在低地址。
  小端存储就是高字节数据放在高地址。
  如下方例子,可以知道使用的处理器的存储方式:

#include<stdio.h>
union Un
{
  int i;
  char c;
};
union Un un;
 
int main()
{
  un.i = 0x11223344;
  if (un.c == 0x11)
  {
    printf("大端\n");
  }
  else if (un.c == 0x44)
  {
    printf("小端\n");
  }  
}

2、指针方式访问

  由于在一个成员长度不同的联合体里,分配给联合体的内存大小取决于它的最大成员的大小。如果内部成员的大小相差太大,当存储长度较短的成员时,浪费的空间是相当可观的,在这种情况下,更好的方法是在联合体中存储指向不同成员的指针而不是直接存储成员本身。所有指针的长度都是相同的,这样能解决内存空间浪费的问题。

#include<stdio.h>
typedef struct
{
  unsigned char a;
  int b;
}stValue1;
 
typedef struct
{
  int c;
  unsigned char d[10];
  double e;
}stValue2;
 
//联合体成员定义为指针成员
union Un
{
  stValue1 *ptrSt1;
  stValue2 *ptrSt2;
};
 
int main()
{
  union Un *info;
  info->ptrSt1->a = 5;
  info->ptrSt2->e = 9.7f;
}

  总之在实际使用联合体union过程中一句话总结:围绕成员互斥和内存共享这两个核心点去灵活设计你的数据结构。


更多技术内容和书籍资料获取敬请关注公众号“明解嵌入式”

1 声望
0 粉丝
0 条评论
推荐阅读
重载的奥义之函数重载
&emsp;&emsp;重载,顾名思义从字面上理解就是重复装载,打一个不恰当的比方,你可以用一个篮子装蔬菜,也可以装水果或者其它,使用的是同一个篮子,但是可以用篮子重复装载的东西不一样。

Sharemaker阅读 265

封面图
2022风云变幻的一年,我开始思考生活的意义
2022 年对所有人来说,是束缚的一年、也是艰难的一年。这一年疫情起起伏伏,商场歇业,饭店关门,在工作之余吃一碗热乎的刀削面也成了奢侈。对一个北漂来说,“回家”和“进京”从未如此艰难。假期好不容易回趟家,结...

杨成功9阅读 1.3k评论 1

封面图
技术社区的朋友们,让我们在 2050 团聚吧!
提到 2050 你会想到什么? ——第一批 00 后步入 50 岁,刚刚出生的孩子们成为这个世界的中流砥柱;如科幻般的世界:上天下地、无尽探索、发达的医疗、先进的交通;

SegmentFault思否5阅读 12.8k评论 1

如果再来一次,你还会选择互联网么?
现在互联网的就业环境,大家都在感受着一股寒意。也不知道从什么时候开始,身边悲观的声音越来越多了。如果再给你一次机会,你还会选择互联网吗?回答这个问题之前,我想跟大家聊聊一个我朋友的故事。他从学渣到...

敖丙8阅读 1.1k评论 2

封面图
大数据 + VR 全景技术重塑“二手车买车场景”
行内人都知道,二手车交易的核心问题在于车况信息不透明。中国二手车交易市场制度尚不完善,长期以来缺少行业公认的车辆估值标准和车况检测标准,二手车商提供的估值和车况信息不够透明。这导致用户和车商交易双...

之家技术13阅读 12k

封面图
Bash 常用脚本片段
这段脚本非常有用,你只要在你的脚本开头加上下面的内容,就能以 --param value 的格式解析参数。由于这段脚本尽可能写的短小不占空间,所以格式方面会要求所有的参数都有值,例如不接受无参数的 --daemon,而必...

捏造的信仰5阅读 1.7k评论 1

百度搜索首届技术创新挑战赛有奖征文|分享百度搜索大赛
有人举手发问:海克斯科技是什么梗?还有人举手发问:KFC🍗打工可以偷吃几块不? 爱美的人问:怎么去除很早一以前的痘印,那块有点黑。北方的朋友也会向南方的朋友发问:大蟑螂🪳是什么呀?南方的朋友也会对奇北方...

SegmentFault思否5阅读 15.7k

封面图
1 声望
0 粉丝
宣传栏