草庐IT

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

明解嵌入式 2023-03-28 原文

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

       简单看下区别:

1 struct MyStruct 
2 {
3     double a;
4     int b;
5     char c;
6 };
7 struct MyStruct value;
1 union MyUnion 
2 {
3     double a;
4     int b;
5     char c;
6 };
7 union MyUnion value;

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

       

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

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

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

  • 1、变量的初始化

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

 1 union MyUnion 
 2 {
 3     double a;
 4     int b;
 5     char c;
 6 };
 7 //为第一个成员初始化
 8 union MyUnion un1 = {5.0f};
 9 //错误初始化,不能为多个成员初始化
10 union MyUnion un1 = {5.0f, 10};
11 //对其它位置的成员进行初始化,则可以通过指定初始化方式
12 union MyUnion un1 = {.b = 10};
13 //与结构体一样,也可以将一个联合体变量作为初始值,直接初始化给同类型的另一个联合体变量
14 union MyUnion un2 = un1;
  • 2、数据位操作

 1 #include<stdio.h>
 2 typedef struct
 3 {
 4   unsigned char bit0:1;
 5   unsigned char bit1:1;
 6   unsigned char bit2:1;
 7   unsigned char bit3:1;
 8   unsigned char bit4:1;
 9   unsigned char bit5:1;
10   unsigned char bit6:1;
11   unsigned char bit7:1;
12 }bitValue;
13  
14 typedef union
15 {
16   unsigned char bytedata;
17   bitValue  bitdata; 
18 }regValue;
19  
20 int main()
21 {
22   regValue data;
23   data.bytedata= 0x5A;
24   printf("%d",data.bitdata.bit5);  //读取第6位
25   data.bitdata.bit7 = 1;           //修改第8位
26   return 0;
27 }

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

  • 3、和struct嵌套使用

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

 1 struct tvFeature    //电视属性
 2 {
 3    char *logo;     //品牌
 4    int price;      //价格
 5    int screensize  //屏幕尺寸  
 6    int resolution  //分辨率 
 7 }tvFeature;
 8 struct tvFeature tvfeature;
 9  
10 struct airFeature  //空调属性
11 {
12    char *logo; //品牌
13    int price;   //价格
14    int coldcapacity;//制冷量 
15    int hotcapacity;//制热量
16 }airFeature;
17 struct airFeature airfeature;

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

 1 struct homeappliancesFeature  //电器属性
 2 {
 3    char *logo; //品牌
 4    int price;   //价格
 5    int screensize  //屏幕尺寸  
 6    int resolution  //分辨率
 7    int coldcapacity;//制冷量 
 8    int hotcapacity;//制热量
 9 }homeappliancesFeature;
10  
11 struct homeappliancesFeature tvfeature;
12 struct homeappliancesFeature airfeature;

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

 1 struct tvFeature    //电视属性
 2 {
 3    int screensize  //屏幕尺寸  
 4    int resolution  //分辨率 
 5 }tvFeature;
 6 struct airFeature  //空调属性
 7 {
 8    int coldcapacity;//制冷量 
 9    int hotcapacity;//制热量
10 }airFeature;
11  
12 struct homeappliancesFeature  //电器属性
13 {
14    char *logo; //品牌
15    long country; //国家
16    union
17    {
18       struct tvFeature tvST;
19       struct airFeature airST;
20    };
21 };
22 struct homeappliancesFeature tvfeature;
23 struct homeappliancesFeature airfeature;

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

  • 4、数据复制

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

 1 typedef union
 2 {
 3   uint8   data8[4];
 4   uint32  data32;
 5 }dataType;
 6  
 7 uint32 sendData = 0x5A5AA5A5;
 8 uint32 receiveData;
 9 dataType commSend;
10 void main(void)
11 {
12     uint8 commData[128];     
13     //数据复制
14     commData.data32 = sendData;    
15     //发送数据,字节复制,不需要再将commData.data32单独移位拆分
16     commData[0]= commSend.data8[0];
17     commData[1]= commSend.data8[1];
18     commData[2]= commSend.data8[2];
19     commData[3]= commSend.data8[3];
20       
21     //读取数据时,字节复制,不需要再将已经读取到的4个单字节数据拼接 
22     receiveData =  commData.data32;  
23 }
  • 5、分时发送不同帧格式数据

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

 1 typedef struct 
 2 { 
 3    uint8 head;   //帧头格式相同
 4    union    //中间数据格式不一样
 5    {
 6       struct             //payloadType1  
 7       {
 8         uint8 cmd;
 9         uint8 type;
10         uint8 data[5];   
11         uint8 check;       
12       }msgType1;
13    
14       struct              //payloadType2    
15       {
16         uint16 cmd;     
17         uint8 data[8];   
18         uint16 check;       
19       }msgType2;  
20           
21      uint8 data[10];      //payloadType3  
22    } payloadType;
23    uint8 end;    //帧尾格式相同
24 }frameType;

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

1、数据大小端

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

 1 #include<stdio.h>
 2 union Un
 3 {
 4   int i;
 5   char c;
 6 };
 7 union Un un;
 8  
 9 int main()
10 {
11   un.i = 0x11223344;
12   if (un.c == 0x11)
13   {
14     printf("大端\n");
15   }
16   else if (un.c == 0x44)
17   {
18     printf("小端\n");
19   }  
20 }

2、指针方式访问

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

 1 #include<stdio.h>
 2 typedef struct
 3 {
 4   unsigned char a;
 5   int b;
 6 }stValue1;
 7  
 8 typedef struct
 9 {
10   int c;
11   unsigned char d[10];
12   double e;
13 }stValue2;
14  
15 //联合体成员定义为指针成员
16 union Un
17 {
18   stValue1 *ptrSt1;
19   stValue2 *ptrSt2;
20 };
21  
22 int main()
23 {
24   union Un *info;
25   info->ptrSt1->a = 5;
26   info->ptrSt2->e = 9.7f;
27 }

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


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

有关在实际应用中联合体union的妙用的更多相关文章

  1. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  2. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  3. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  4. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  5. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  6. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  7. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  10. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

随机推荐