飞道的博客

干货 | 结构体、联合体嵌套使用的一些实用操作

402人阅读  评论(0)

结构体、联合体是C语言中的构造类型,结构体我们平时应该都用得很多。但是,对于联合体,一些初学的朋友可能用得并不多,甚至感到陌生。我们先简单看一下联合体:

在C语言中定义联合体的关键字是union

定义一个联合类型的一般形式为:


   
  1. union 联合名
  2. {
  3. 成员表
  4. };

成员表中含有若干成员,成员的一般形式为:类型说明符 成员名。其占用的字节数与成员中最大数据类型占用的字节数。

下面我们一起看一下结构体、联合体结合使用在C语言、嵌入式中的一些实用技巧。

1、应用于管理不同的数据

示例代码:


   
  1. enum DATA_PKG_TYPE
  2. {
  3.     DATA_PKG1 =  1,
  4.     DATA_PKG2,
  5.     DATA_PKG3    
  6. };
  7. struct data_pkg1
  8. {
  9.      // ...
  10. };
  11. struct data_pkg2
  12. {
  13.      // ...
  14. };
  15. struct data_pkg3
  16. {
  17.      // ...
  18. };
  19. struct data_pkg
  20. {
  21.     enum DATA_PKG_TYPE data_pkg_type;
  22.     union 
  23.     {
  24.         struct data_pkg1 data_pkg1_info;
  25.    struct data_pkg2 data_pkg2_info;
  26.    struct data_pkg3 data_pkg3_info;
  27.     }data_pkg_info;
  28. };

这里把struct data_pkg1、struct data_pkg2、struct data_pkg3三个结构体放到了struct data_pkg这个结构体里进行管理,把data_pkg_type与union里的三个结构体建立一一对应关系,我们需要用哪一结构体数据就通过data_pkg_type来进行选中。

在进行数据组包的时候,先给data_pkg_type进行赋值,确定数据包的类型,再给对应的union里的结构体进行赋值;在进行数据解析的时候,通过data_pkg_type来选择解析哪一组数据。

思考一下,如果在union里面再嵌套一层union会怎么样?会变得更复杂?以前的话,我会觉得越嵌套会越复杂,我也很抵制这种不断嵌套的做法。但后来看了我同事鱼鹰(公众号:鱼鹰谈单片机)的设计之后,我惊呆了!这可太秀了,他就是这么嵌套使用把原本复杂的系统数据管理得明明白白的。我们看他怎么设计的(看个大概的图):

可以看到最左边和最右边这就建立起了一一对应关系,我们的模块很多,数据很多,但是在这样的设计中显得很清晰、很容易维护。

2、寄存器、状态变量封装

我们看一看TI的寄存器封装是怎么做的:

所有的寄存器被封装成联合体类型的,联合体里边的成员是一个32bit的整数及一个结构体,该结构体以位域的形式体现。这样就可以达到直接操控寄存器的某些位了。比如,我们要设置PA0引脚的GPAQSEL1寄存器的[1:0]两位都为1,则我们只操控两个bit就可以很方便的这么设置:

GpioCtrlRegs.GPAQSEL1.bit.GPIO0 = 3

或者直接操控整个寄存器:

GpioCtrlRegs.GPAQSEL1.all |=0x03 

位域相关文章:【C语言笔记】位域

如果不是工作于芯片原厂,寄存器的封装应该离我们很远。但我们可以学习使用这种方法,然后用于我们的实际应用开发中。

下面就看一种实际应用:管理一些状态变量

示例代码:


   
  1. union sys_status
  2. {
  3.     uint32 all_status;
  4.     struct 
  5.    {
  6.        bool status1:   1// FALSE / TRUE
  7.        bool status2:   1// 
  8.        bool status3:   1// 
  9.        bool status4:   1// 
  10.        bool status5:   1// 
  11.        bool status6:   1// 
  12.        bool status7:   1// 
  13.        bool status8:   1// 
  14.        bool status9:   1// 
  15.        bool status10:  1// 
  16.     // ...
  17.   }bit;
  18. };

之前记得群里有一位小伙伴问系统有几十个状态变量需要管理,怎么做比较好。如上例子就是比较好的一种管理方法。

3、数据组合/拆分、大小端

(1)验证大小端


   
  1. #include <stdio.h>
  2. typedef unsigned  int  uint32_t;
  3. typedef unsigned char uint8_t;
  4. union bit32_data
  5. {
  6.     uint32_t data;
  7.      struct 
  8.     {
  9.         uint8_t byte0;
  10.         uint8_t byte1;
  11.         uint8_t byte2;
  12.         uint8_t byte3;
  13.     } byte;
  14. };
  15. int main(void)
  16. {
  17.     union bit32_data num;
  18.     
  19.     num.data =  0x12345678;
  20.  
  21.   if ( 0x78 == num. byte.byte0)
  22.   {
  23.    printf( "Little endian\n");
  24.  }
  25.   else  if ( 0x78 == num. byte.byte3)
  26.  {
  27.    printf( "Big endian\n");
  28.  } else{}
  29.      return  0;
  30. }

运行结果:

(2)数据组合、拆分

这其实也就是上一篇文章《面试题 | 获取整数各个字节》介绍的。在数据组合与拆分之前首先需要确实当前平台的大小端。比如小编使用的平台是小端模式

① 把0x12345678拆分成0x78、0x56、0x34、0x12:


   
  1. #include <stdio.h>
  2. typedef unsigned  int  uint32_t;
  3. typedef unsigned char uint8_t;
  4. union bit32_data
  5. {
  6.     uint32_t data;
  7.      struct 
  8.     {
  9.         uint8_t byte0;
  10.         uint8_t byte1;
  11.         uint8_t byte2;
  12.         uint8_t byte3;
  13.     } byte;
  14. };
  15. int main(void)
  16. {
  17.     union bit32_data num;
  18.     
  19.     num.data =  0x12345678;
  20.     printf( "byte0 = 0x%x\n", num. byte.byte0);
  21.     printf( "byte1 = 0x%x\n", num. byte.byte1);
  22.     printf( "byte2 = 0x%x\n", num. byte.byte2);
  23.     printf( "byte3 = 0x%x\n", num. byte.byte3);
  24.      return  0;
  25. }

运行结果:

② 把0x78、0x56、0x34、0x12组合成0x12345678:


   
  1. #include <stdio.h>
  2. typedef unsigned  int  uint32_t;
  3. typedef unsigned char uint8_t;
  4. union bit32_data
  5. {
  6.     uint32_t data;
  7.      struct 
  8.     {
  9.         uint8_t byte0;
  10.         uint8_t byte1;
  11.         uint8_t byte2;
  12.         uint8_t byte3;
  13.     } byte;
  14. };
  15. int main(void)
  16. {
  17.     union bit32_data num;
  18.     
  19.     num. byte.byte0 =  0x78;
  20.  num. byte.byte1 =  0x56;
  21.  num. byte.byte2 =  0x34;
  22.  num. byte.byte3 =  0x12;
  23.     printf( "num.data = 0x%x\n", num.data);
  24.      return  0;
  25. }

运行结果:

但是数据组合与拆分有更好的方法:移位操作。篇幅有限不再贴出代码,详细代码可参考:《面试题 | 获取整数各个字节》《C语言、嵌入式位操作精华技巧大汇总》两篇文章。

4、结构体 & 缓冲区


   
  1. #define BUF_SIZE  16
  2. union protocol_data
  3. {
  4.  uint8_t data_buffer[BUF_SIZE];
  5.   struct 
  6.  {
  7.   uint8_t data1;
  8.   uint8_t data2;
  9.   uint8_t data3;
  10.   uint8_t data4;
  11.    // ...
  12.  }data_info;
  13. };

这种应用得很广泛,用于自定义通信协议。struct里面的内容可以设计得很简单,比如全是有用的数据,或是设计得很复杂,包含一些协议头尾、包长、有效数据、校验等内容。

但无论如何,我们组包发送的过程是填充结构体->发送data_buffer;反之接收数据解析的过程就是接收数据存于data_buffer->使用结构体数据。我们之前分享的《干货 | protobuf-c之嵌入式平台使用》也是这个思路。

5、传输浮点数据


   
  1. union f_data 
  2. {
  3.  float f;
  4.   struct
  5.  {
  6.   unsigned char  byte[ 4];
  7.  };
  8. }

类似的,使用这样子的方法可以用于传输浮点数,更具体地不再展开,网络上有很多这一块的资料。感兴趣的朋友可以自己操作验证验证。

最后

以上就是本次的分享,如果觉得文章不错,转发、在看,也是我们继续更新的动力。

猜你喜欢:

2020年精选原创笔记汇总

长文 | 一些Linux知识汇总

硬核 | 关于Linux内核的简明知识

1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。在公众号聊天界面回复1024,即可免费获取!


转载:https://blog.csdn.net/zhengnianli/article/details/111503438
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场