在定义结构体和共用体时,都是{}里面写成员,都是自定义复合类型,都可以嵌套、都可以做数组、指针。上节我们重点分析了结构体,本文将主要讲共用体。
一、定义
共用体是一种自定义数据类型,多个成员共享同一块空间。
同一时刻,只有一个成员是有效的;给一个成员赋值,会覆盖其他成员的值。
共用体占用的内存大小:等于它最大成员所占的字节数。
#include<stdio.h>#include<windows.h>#include<math.h>uniondata{inti;charc;floatf;};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);uniondata d;printf("共用体data占:%d字节\n",sizeof(d));d.i=0x68;printf("i=%lx,c=%c\n",d.i,d.c);d.f=3.14f;//覆盖整个内存printf("f=%f,i=%lx\n",d.f,d.i);return0;}运行结果:
赋值完d.f之后,再读d.i属于类型双关,C标准未定义这种行为,只是大部分编译器可以跑。
对于值的改变,我们通过下面这个例子可以清晰的看到,后面成员变量的赋值,直接把原来的值进行了部分覆盖:
#include<stdio.h>#include<windows.h>#include<math.h>uniondata{inta;shortb;};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);uniondata s1;s1.a=0x01020304;s1.b=0x0102;printf("a=0x%X\n",s1.a);return0;}运行结果:
二、使用场景
1、类型双关
把同一块内存,以不同类型视角解读。eg:把float二进制位当作整数查看
unionu{floatff;unsignedintii;};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);unionu test;test.ff=1.0f;printf("ff=%f,ii=%lx\n",test.ff,test.ii);return0;}运行结果:
2、节省空间
多个变量不会同时使用,放在union共用一块内存,减少内存占用。
structmag{inttype;uniondata{intival;floatfval;charcval;};};例如:type=0,可以代表ival,type=1,代表fval;type=2代表cval
Msg结构体必须带type标记,工程上用union一定要加标记位,否则不知道哪个成员有效。
(3)硬件、网络协议、二进制报文解析
协议字段中,同一个位置,根据标志位不同代表不同含义,用union非常适合
(4)寄存器操作(嵌入式)
硬件寄存器,既可以整体读写,也可以按位/字节访问,使union变量互斥不同时使用,需要同一块内存做多种类型解释,内存紧张(嵌入式)
三、注意事项
1、同一时刻只有一个成员有效**(容易忽视,再重复)**
2、大小是最大成员大小,但是考虑内存对齐
unionu{chararr[5];inta;}上面最大成员是5字节,但是对齐是按照int(4),sizeof(U)=8,不是5
3、不要混用指针
不要拿union不同成员的指针随意强制转化,容易对齐问题
4、联合体不能直接整体比较if(u1==u2),C语言不支持,需要自己逐个成员对比
5、大小端问题
CPU内部寄存器是按照bit取存储数据,但是CPU内部寄存器资源有限,需要把计算好的数据放到内存中,对于内存来说,它没有bit位的概念,是按照字节编号(地址)来寻址。把寄存器中数据存到内存,按照哪种顺序保存就是大小端的问题。
小端模式:数据的高位存到内存的高地址,数据的低位到内存的低地址(现阶段使用小端的比较多)。
大端模式:数据的高位存到内存的低地址,数据的低位存到内存的高地址。
用union拆分字节看底层二进制,程序受CPU大小端影响,跨平台代码要小心。
判断大小端的方法:
#include<stdio.h>#include<windows.h>#include<math.h>unionEndianCheck{unsignedintval;unsignedcharbytes[4];};intmain(){SetConsoleOutputCP(65001);SetConsoleCP(65001);unionEndianCheck test;test.val=0x01020304;printf("val=0x%08X\n",test.val);printf("bytes[0]=0x%02x bytes[1]=0x%02x bytes[2]=0x%02x bytes[3]=0x%02x\n",test.bytes[0],test.bytes[1],test.bytes[2],test.bytes[3]);//对于寄存器,右边是低位;对内存,左边是低地址。if(test.bytes[0]==0x04){printf("本机:小端模式\n");}else{printf("本机:大端模式\n");}return0;}本机运行结果:
四、结构体和共用体的区别
口诀:
结构体:各占各的,全部共存,内存相加(对齐后)
共用体:抢占同步一块,互斥共存,取最大(对齐后)
| 特性 | struct结构体 | union共用体 |
|---|---|---|
| 内存布局 | 每个成员独立分配内存 | 所有成员共享同一块内存 |
| 总大小 | =全部成员总和 | =最大成员大小(对齐后) |
| 生命周期 | 全部成员同时有效 | 同一时刻只有一个成员有效 |
| 用途 | 存放一组同时存在的属性 | 互斥数据,类型双关,省内存 |