C语言union用于打包和拆包数据

使用union来打包/拆包数据

联合的成员存储在共享存储区中。这是使我们能够找到有趣的工会应用程序的关键功能。
考虑下面的联合:

  1. union {
  2.       uint16_t   word;
  3.       struct {
  4.               uint8_t   byte1;
  5.               uint8_t   byte2;
  6.               };
  7. } u1;

复制代码

此union内部有两个成员:第一个成员“ word”是一个两字节的变量。第二个成员是两个单字节变量的结构。为联合分配的两个字节在其两个成员之间共享。
分配的内存空间可以如下图1所示。


图1
“ word”变量是指整个分配的内存空间,“ byte1”和“ byte2”变量是指构成“ word”变量的一字节区域。我们如何使用此功能?假设您有两个单字节变量“ x”和“ y”,应将其组合以产生单个两字节变量。

在这种情况下,您可以使用上述联合并为结构成员分配“ x”和“ y”,如下所示:

  1. u1.byte1 = y;
  2. u1.byte2 = x;

复制代码

现在,我们可以读取并集的“ word”成员,以获得由“ x”和“ y”变量组成的两字节变量(参见图2)。


图2
上面的示例显示了使用并集将两个一个字节的变量打包为单个两个字节的变量。我们也可以做相反的事情:将两个字节的值写入“ word”,然后通过读取“ x”和“ y”变量将其解压缩为两个一个字节的变量。将值写入工会的一个成员并读取工会的另一个成员有时被称为“数据修剪”。

处理器字节序
当使用联合对数据进行打包时,我们需要注意处理器的字节顺序。正如罗伯特·基姆(Robert Keim)关于字节序的文章所讨论的那样,该术语指定了数据对象的字节在内存中存储的顺序。处理器可以是小端或大端。使用big-endian处理器时,数据的存储方式是,包含最高有效位的字节具有最低的内存地址。在小端系统中,包含最低有效位的字节首先存储。
图3中所示的示例说明了序列0x01020304的小端和大端存储。


图3.图片由IAR提供。

让我们使用以下代码尝试上一节的并集:

  1. #include <stdio.h>
  2. #include <stdint.h>
  3.  
  4. int main()
  5. {
  6.   
  7. union {
  8.   struct{
  9.     uint8_t   byte1;
  10.     uint8_t   byte2;
  11.   };
  12.   uint16_t    word;
  13.    
  14. } u1;
  15.   
  16.   
  17. u1.byte1 = 0x21;
  18. u1.byte2 = 0x43;
  19.   
  20. printf("Word is: %#X", u1.word);
  21. return 0;
  22. }

复制代码

运行此代码,我得到以下输出:
词是:0X4321
这表明共享存储空间的第一个字节(“ u1.byte1”)用于存储“ word”变量的最低有效字节(0X21)。换句话说,我用来执行代码的处理器是Little Endian。
如您所见,联合的特定应用程序可能表现出与实现有关的行为。但是,这不是一个严重的问题,因为对于这样的低级编码,我们通常知道处理器的字节序。如果我们不知道这些细节,我们可以使用上面的代码来查找数据在内存中的组织方式。

替代解决方案

除了使用并集,我们还可以使用按位运算符来执行数据打包或拆包。例如,我们可以使用以下代码来组合两个一个字节的变量“ byte3”和“ byte4”,并产生一个单个的两个字节的变量(“ word2”):

  1. word2 = (((uint16_t)   byte3) << 8 ) | ((uint16_t)   byte4);

复制代码

让我们比较一下小端和大端两种情况下这两种解决方案的输出。考虑下面的代码:

  1. #include <stdio.h>
  2. #include <stdint.h>
  3.  
  4. int main()
  5. {
  6. union {
  7.       struct {
  8.               uint8_t   byte1;
  9.               uint8_t   byte2;
  10.              };
  11.       uint16_t    word1;
  12.       } u1;
  13.   
  14. u1.byte1 = 0x21;
  15. u1.byte2 = 0x43;
  16. printf("Word1 is: %#X\n", u1.word1);
  17.   
  18. uint8_t       byte3, byte4;
  19. uint16_t      word2;
  20. byte3 = 0x21;
  21. byte4 = 0x43;
  22. word2 = (((uint16_t) byte3) << 8 ) | ((uint16_t) byte4);
  23. printf("Word2 is: %#X \n", word2);
  24.   
  25. return 0;
  26. }

复制代码

如果我们针对大型字节序处理器(例如TMS470MF03107)编译此代码,则输出为:

Word1是:0X2143
Word2是:0X2143

但是,如果我们针对像STM32F407IE这样的小端序处理器对其进行编译,则输出将是:

Word1是:0X4321
Word2是:0X2143

尽管基于联合的方法表现出与硬件有关的行为,但是基于移位操作的方法却得到相同的结果,而不管处理器的字节顺序如何。这是由于以下事实:在后一种方法中,我们为变量的名称(“ word2”)分配了一个值,并且编译器负责该设备使用的内存组织。但是,使用基于联合的方法,我们正在更改构成“ word1”变量的字节的值。
尽管基于联合的方法表现出与硬件有关的行为,但它具有更易读和可维护的优点。这就是为什么许多程序员更喜欢在该应用程序中使用联合的原因。

“数据校正”的实际示例  
在使用常见的串行通信协议时,我们可能需要执行数据打包或拆包。考虑一个串行通信协议,该协议在每个通信序列期间发送/接收一个字节的数据。只要我们使用一字节长的变量,就很容易传输数据,但是如果我们有一个任意大小的结构应该通过通信链接怎么办?在这种情况下,我们必须以某种方式将数据对象表示为一字节长的变量数组。一旦获得了字节数组表示,就可以通过通信链接传输字节。然后,在接收器端,我们可以适当地打包它们并重建原始结构。

例如,假设我们需要通过UART通信发送一个浮点变量“ f1”。浮点变量通常占用四个字节。因此,我们可以将以下并集用作提取“ f1”的四个字节的缓冲区:

  1. union {
  2.       float    f;
  3.       struct {
  4.              uint8_t   byte[4];
  5.              };
  6. } u1;

复制代码

发送器将变量“ f1”写入联合的float成员。然后,它读取“字节”数组并将字节发送到通信链接。接收器进行相反的操作:它将接收到的数据写入其自己的并集的“字节”数组,并读取并集的float变量作为接收值。我们可以使用这种技术来传输任意大小的数据对象。以下代码可以作为验证此技术的简单测试

  1. #include <stdio.h>
  2. #include <stdint.h>
  3.  
  4. int main()
  5. {
  6.  
  7. float f1=5.5;
  8.   
  9. union buffer {
  10.              float f;
  11.              struct {
  12.                     uint8_t byte[4];
  13.                     };
  14.              };
  15.   
  16. union buffer buff_Tx;
  17. union buffer buff_Rx;
  18. buff_Tx.f = f1;
  19. buff_Rx.byte[0] = buff_Tx.byte[0];
  20. buff_Rx.byte[1] = buff_Tx.byte[1];
  21. buff_Rx.byte[2] = buff_Tx.byte[2];
  22. buff_Rx.byte[3] = buff_Tx.byte[3];
  23.   
  24. printf("The received data is: %f", buff_Rx.f);
  25.   
  26. return 0;
  27. }

复制代码

下面的图4展示了所讨论的技术。请注意,字节是顺序传输的。


图4
结论
联合的原始应用程序创建了互斥变量的共享存储区,但随着时间的流逝,程序员已经广泛使用联合用于完全不同的应用程序:使用联合进行数据打包/拆包。工会的这种特殊应用涉及将值写入工会的一个成员并读取工会的另一个成员。

“数据修剪”或使用联合进行数据打包/拆包可能导致依赖于硬件的行为。但是,它具有更具可读性和可维护性的优点。这就是为什么许多程序员更喜欢在该应用程序中使用联合的原因。当我们有任意大小的数据对象应通过串行通信链接时,“数据修剪”将特别有用。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言中的union和struct都是用来定义复合数据类型的。 struct是结构体,它可以定义不同类型的变量,这些变量的内存空间是连续的,并且各自有独立的内存地址。 union是联合体,它也可以定义不同类型的变量,但是它们共用一块内存空间,并且只有一个变量能够占用这块内存空间。 所以 union 和 struct 的主要区别在于内存空间的使用方式不同,struct是独立占用内存空间,而union是共用一块内存空间。 ### 回答2: C语言中的union和struct都是用来定义自定义数据类型的结构体。但是它们的差别很大,下面让我们一起从以下5个方面来分析它们两者的不同。 1.占用空间: 虽然struct和union都能按照一定的结构描述数据,但是它们在占用内存空间方面的处理是不同的。struct会根据它的每一个员所占用的空间之和来占用一定的内存空间,而union则是会占用其中最大元素所占用的空间,这可以使得内存的利用率更高。 2.员存储顺序: 和内存空间利用率有关,union员存储是重叠存储,也就是各个员是存储在同一个内存使用空间中,而struct的员是按照定义的时候的顺序一步一步分配使用空间的,这就意味着你可以直接通过对内存地址的控制,来实现多个数据项存储到一块内存中的操作。 3.员使用方式: union只能使用集合中的一个元素,而struct是可以使用多个元素的,也就是说,当你需要一个集合中的多个元素的时候,你可以使用struct;而当你需要一个同样的集合类型,三个元素之间是进行互换的时候可以使用union。 4.表示方式: 在C语言中,union和struct都是用来描述自定义数据类型的,但是它们两者的表述语句是不同的:struct使用“struct 数据类型名”形式进行定义,而union使用“union 数据类型名”形式进行定义。 5.联合体和结构体访问员方式: struct 结构体名称.结构体员名称; union 联合体名称.联合体员名称; union可以使用相同的内存存储一个或多个不同的数据类型,减少内存使用。在需要少量的存储空间(例如在编写驱动程序时)时,联合也是特别有用的。而struct可以使用不同的类型来为新的数据类型定义集合。当你需要使用struct中的多个元素时,应该使用struct。 ### 回答3: C语言中,结构体(struct)和共用体(union)是两个非常基础的数据类型。虽然它们都用于构建数据类型,但它们有一些基本的区别。 首先,结构体是一个用户定义的数据类型,可以包含多个不同的数据类型,例如整数,浮点数,字符等。它们可以按名称访问并分配不同的空间。结构体类型之间的区别在于它们包含的数据类型的数量和顺序的不同。 共用体也是一个用户定义的数据类型,它们和结构体一样可以包含多个不同的数据类型,但只能一次使用其中一个员。这是因为共用体中的每个员都从同一地址开始,因此对该地址的任何更改都会影响全部员。 这意味着可以根据需要将共用体的一个员视为当前使用的员。这可以节省存储单元,还可以方便地实现相同类型数据的联合或混淆。相反,如果要使用结构体中的一个员,必须访问一整个结构体变量,并且不能像共用体那样共享存储单元。 另外,结构体的大小取决于其员大小的总和,而共用体的大小则等于最大员的大小。这是因为共用体是一个只包含一个员的数据类型,因此必须能够容纳所有可能的员类型,而在同一时间只能有一种类型是活动的。 这些是结构体和共用体的最基本的区别。在C语言的编程中,了解它们的用途和使用场合非常有用。对于大型程序,可以使用结构体来将数据打包在一起以便于传递和组织。共用体则常用于只需要存储一种类型的结果,例如通过协议传输时需要打包数据,它们的大小和内存使用也要比结构体更高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值