用于打包和解包数据的 C 语言联合
了解如何使用 C 语言中的联合打包和解包数据。
了解如何在 C 语言中使用联合打包和解包数据。
在上一篇文章中,我们讨论了联合的最初应用一直在为互斥变量创建共享内存区域。然而,随着时间的推移,程序员已经广泛地将联合用于一个完全不同的应用程序:从一个更大的数据对象中提取更小的数据部分。在本文中,我们将更详细地研究联合的这种特殊应用。
使用联合来打包/解包数据
联合的成员存储在共享内存区域中。这是使我们能够找到有趣的工会应用程序的关键功能。
考虑下面的联合:
union { uint16_t 字;结构 { uint8_t 字节 1; uint8_t 字节 2; };} u1;
这个联合中有两个成员:第一个成员,“word”,是一个两字节的变量。第二个成员是两个单字节变量的结构。为联合分配的两个字节在其两个成员之间共享。
分配的内存空间可以如下图1所示。
图 1
“word”变量指的是整个分配的内存空间,“byte1”和“byte2”变量指的是构成“word”变量的一字节区域。我们如何使用这个功能?假设您有两个单字节变量“x”和“y”,应将它们组合起来生成一个单字节变量。
在这种情况下,您可以使用上述联合并将“x”和“y”分配给结构成员,如下所示:
u1.byte1 =y;u1.byte2 =x;
现在,我们可以读取联合的“word”成员,得到一个由“x”和“y”变量组成的两字节变量(见图2)。
图 2
上面的例子展示了如何使用联合将两个一字节的变量打包成一个两字节的变量。我们也可以反过来:将一个两字节的值写入“word”,然后通过读取“x”和“y”变量将其解包为两个一字节的变量。将值写入联合的一个成员并读取它的另一个成员有时被称为“数据双关”。
处理器字节序
当使用联合来打包/解包数据时,我们需要注意处理器的字节顺序。正如罗伯特·凯姆 (Robert Keim) 关于字节序的文章中所讨论的那样,该术语指定了数据对象的字节在内存中的存储顺序。处理器可以是小端或大端。对于大端处理器,数据的存储方式是包含最高有效位的字节具有最低的内存地址。在 little-endian 系统中,首先存储包含最低有效位的字节。
图 3 中的示例说明了序列 0x01020304 的小端和大端存储。
图 3。 图片由 IAR 提供。
我们用下面的代码来试验一下上一节的并集:
#include <stdio.h>#include <stdint.h>int main(){ union { struct{ uint8_t byte1; uint8_t 字节 2; }; uint16_t 字; } u1; u1.byte1 =0x21;u1.byte2 =0x43; printf("字是:%#X", u1.word);return 0;}
运行此代码,我得到以下输出:
字为:0X4321
这表明共享内存空间的第一个字节(“u1.byte1”)用于存储“word”变量的最低有效字节(0X21)。换句话说,我用来执行代码的处理器是小端的。
如您所见,联合的这种特殊应用可以表现出依赖于实现的行为。然而,这应该不是一个严重的问题,因为对于这种低级编码,我们通常知道处理器的字节序。如果我们不知道这些细节,我们可以使用上面的代码来了解数据在内存中是如何组织的。
替代方案
除了使用联合之外,我们还可以使用按位运算符来执行数据打包或解包。例如,我们可以使用以下代码将两个单字节变量“byte3”和“byte4”组合起来,生成一个单字节变量(“word2”):
word2 =(((uint16_t) byte3) <<8 ) | ((uint16_t) byte4);
让我们在小端和大端情况下比较这两种解决方案的输出。考虑下面的代码:
#include <stdio.h>#include <stdint.h>int main(){union { struct { uint8_t byte1; uint8_t 字节 2; }; uint16_t word1; } u1; u1.byte1 =0x21;u1.byte2 =0x43;printf("Word1 是:%#X\n", u1.word1); uint8_t byte3, byte4;uint16_t word2;byte3 =0x21;byte4 =0x43;word2 =(((uint16_t) byte3) <<8 ) | ((uint16_t) byte4);printf("Word2 是:%#X \n", word2);返回 0;}
如果我们为大端处理器(例如TMS470MF03107)编译此代码 ,输出将是:
Word1 是:0X2143
Word2 是:0X2143
但是,如果我们为诸如STM32F407IE之类的小端处理器编译它 ,输出将是:
Word1 是:0X4321
Word2 是:0X2143
虽然基于联合的方法表现出依赖于硬件的行为,但基于移位操作的方法会导致相同的结果,而不管处理器字节序如何。这是因为,使用后一种方法,我们为变量的名称(“word2”)赋值,编译器负责设备使用的内存组织。但是,使用基于联合的方法,我们正在更改构造“word1”变量的字节值。
尽管基于联合的方法表现出依赖于硬件的行为,但它具有更具可读性和可维护性的优点。这就是为什么许多程序员更喜欢在这个应用程序中使用联合。
“数据双关”的实际例子
在使用常见的串行通信协议时,我们可能需要进行数据打包或解包。考虑在每个通信序列期间发送/接收一个字节数据的串行通信协议。只要我们使用一字节长的变量,就很容易传输数据,但是如果我们有一个应该通过通信链接的任意大小的结构呢?在这种情况下,我们必须以某种方式将我们的数据对象表示为一个一字节长的变量数组。一旦我们得到这个字节数组表示,我们就可以通过通信链路传输字节。然后,在接收端,我们可以对它们进行适当的打包,重建原始结构。
例如,假设我们需要通过 UART 通信发送一个浮点变量“f1”。一个浮点变量通常占用四个字节。因此,我们可以使用以下联合作为缓冲区来提取“f1”的四个字节:
union { float f;结构 { uint8_t 字节 [4]; };} u1;
发送器将变量“f1”写入联合的浮点成员。然后,它读取“字节”数组并将字节发送到通信链路。接收者做相反的事情:它将接收到的数据写入自己联合的“字节”数组,并读取联合的浮点变量作为接收值。我们可以使用这种技术来传输任意大小的数据对象。以下代码可以作为验证该技术的简单测试。
#include <stdio.h>#include <stdint.h>int main(){float f1=5.5;联合缓冲区 { 浮动 f;结构 { uint8_t 字节 [4]; }; };联合缓冲区 buff_Tx;联合缓冲区 buff_Rx;buff_Tx.f =f1;buff_Rx.byte[0] =buff_Tx.byte[0];buff_Rx.byte[1] =buff_Tx.byte[1];buff_Rx.byte[2] =buff_Tx .byte[2];buff_Rx.byte[3] =buff_Tx.byte[3]; printf("接收到的数据为:%f", buff_Rx.f);返回 0;}
下面的图 4 显示了所讨论的技术。注意字节是顺序传输的。
图 4
结论
虽然联合的最初应用是为互斥变量创建共享内存区域,但随着时间的推移,程序员已经广泛地将联合用于完全不同的应用:使用联合进行数据打包/解包。联合的这种特殊应用涉及向联合的一个成员写入一个值并读取它的另一个成员。
“数据双关”或使用联合进行数据打包/解包会导致依赖于硬件的行为。但是,它具有更具可读性和可维护性的优点。这就是为什么许多程序员更喜欢在这个应用程序中使用联合的原因。当我们有一个任意大小的数据对象应该通过串行通信链接时,“数据双关”尤其有用。
要查看我的文章的完整列表,请访问此页面。
嵌入式