目录:
一、union的定义说明使用
1、union的定义
2、union的说明
3、union的使用
二、联合体union的基本特性——和struct的同与不同
三、双刃剑——多种访问内存途径共存
四、联合体union和大小端(big-endian、little-endian)
五、联合体union所占内存空间大小
六、联合体union适用场合
七、union本质与进阶
八、联合与结构
九、union巧妙地实现多字节数据类型之间的转化
一、union的定义说明使用
1、union的定义
例如:
union test{ test() { } int office; char teacher[5];};
定义一个名为test的联合类型,它含有两个成员,一个为整型,成员名office;另一个字符数组,数组名为teacher。联合定义之后,即可进行联合变量说明,被说明为test类型的变量,可以存放整型量office或存放字符数组teacher。
2、union的说明
联合变量的说明有三种形式:先定义再说明、定义同时说明和直接说明。以test类型为例,说明如下:
union test{ int office; char teacher[5];};union test a,b;
——————————–
union test{ int office; char teacher[5];} a,b;
——————————–
union{ int office; char teacher[5];} a,b;
经说明后的a,b变量均为test类型。a,b变量的长度应等于test的成员中最长的长度,即等于teacher数组的长度,共5个字节。a,b变量如赋予整型值时,只使用了4个字节,而赋予字符数组时,可用5个字节。
3、union的使用
对联合变量的赋值,使用都只能是对变量的成员进行。联合变量的成员表示为:联合变量名.成员名
例如,a被说明为test类型的变量之后,可使用a.class、a.office不允许只用联合变量名作赋值或其它操作,也不允许对联合变量作初始化赋值,赋值只能在程序中进行。
还要再强调说明的是,一个联合变量,每次只能赋予一个成员值。换句话说,一个联合变量的值就是联合变员的某一个成员值。
二、联合体union的基本特性-和struct的异同
union,中文名“联合体、共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。
不过区别也挺明显:
结构体(struct)中所有变量是“共存”的——优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
而联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间。
三、双刃剑-多种访问内存途径共存
#include "stdio.h"union var{long int l;int i;};int main(void){union var v;v.l = 5;printf("v.l is %d\n",v.i);v.i = 6;printf("now v.l is %ld! the address is %p\n",v.l,&v.l);printf("now v.i is %d! the address is %p\n",v.i,&v.i);}
结果:
v.l is 5
now v.l is 6! the address is 0xbfad1e2c
now v.i is 6! the address is 0xbfad1e2c
所以说,管union的叫共用体还真是贴切——完全就是共用一个内存首地址,并且各种变量名都可以同时使用,操作也是共同生效。如此多的access内存手段,确实好用,不过这些“手段”之间却没法互相屏蔽——就好像数组+下标和指针+偏移一样。
上例中我改了v.i的值,结果v.l也能读取,那么也许我还以为v.l是我想要的值呢,因为上边提到了union的内存首地址肯定是相同的,那么还有一种情况和上边类似:
一个int数组变量a,一个long int(32位机中,long int占4字节,与int相同)变量b,我即使没给int变量b赋值,因为数据类型相同,我使用int变量b也完全会拿出int数组a中的a[0]来,一些时候一不小心用上,还以为用的就是变量b呢。这种逻辑上的错误是很难找出来的(只有当数据类型相去甚远的时候稍好,出个乱码什么的很容易发现错误)。
四、联合体union和大小端(big-endian、little-endian)
下边示范了一种用途,代表四个含义的四个变量,但是可以用一个int来操作,直接int赋值,无论内存访问(指针大小的整数倍,访问才有效率),还有时间复杂度(一次和四次的区别,而且这四次有三次都是不整齐的地址),都会低一些。
#include"stdio.h"union var{char c[4];int i;};int main(void){union var data;data.c[0] = 0x04;//因为是char类型,数字不要太大,算算ascii的范围~data.c[1] = 0x03;//写成16进制为了方便直接打印内存中的值对比data.c[2] = 0x02;data.c[3] = 0x11;//数组中下标低的,地址也低,按地址从低到高,内存内容依次为:04,03,02,11。总共四字节!//而把四个字节作为一个整体(不分类型,直接打印十六进制),应该从内存高地址到低地址看,0x11020304,低位04放在低地址上。printf("%x\n",data.i);}
结果:
11020304
证明我的32位Win7是小端(Little-endian)。
五、联合体union所占内存空间大小
前边说了,首先union的首地址是固定的,那么union到底总共有多大?
根据:分配栈空间的时候内存地址基本上是连续的,至少同类型能保证在一起,连续就说明,如果弄三个结构体出来,他们三个地址应该连着,看一下三个地址的间隔就知道了。
#include "stdio.h"union sizeTest{int a;double b;};int main(void){union sizeTest unionA;union sizeTest unionB;union sizeTest unionC;printf("the initial address of unionA is %p\n",&unionA);printf("the initial address of unionB is %p\n",&unionB);printf("the initial address of unionC is %p\n",&unionC);}
打印,可以看到结果:
the initial address of unionA is 0xbf9b8df8
the initial address of unionB is 0xbf9b8e00
the initial address of unionC is 0xbf9b8e08
很容易看出,8,0,8,这间隔是8字节,按double走的。
怕不保险,再改一下,把int改成数组,其他不变:
union sizeTest{int a[10]; //4*10=40double b;};
打印:
the initial address of unionA is 0xbfbb7738
the initial address of unionB is 0xbfbb7760
the initial address of unionC is 0xbfbb7788
88-60=0x28
60-38=0x28
那么0x28就是40个字节,正好是数组a(4*10=40)的大小。
忘了提一个功能——sizeof( )
用sizeof直接看,就知道union的大小了
printf("the sizeofof unionA is %d\n",sizeof(unionA));printf("the sizeofof unionB is %d\n",sizeof(unionB));printf("the sizeofof unionC is %d\n",sizeof(unionC));printf("the sizeofof union is %d\n",sizeof(union sizeTest));
上边说的地址规律,没有特定规则,也可能和编译器有关。另外,那只是栈空间,还可以主动申请堆空间,当然,堆空间就没有连续不连续一说了。
六、联合体union适用场合
有了前边那个验证,基本可以确认,union的内存是照着里边占地儿最大的那个变量分的。
也就可以大胆的推测一下,这种union的使用场合,是各数据类型各变量占用空间差不多并且对各变量同时使用要求不高的场合。
像上边做的第二个测试,一个数组(或者更大的数组int a[100]),和一个或者几个小变量写在一个union里,实在没什么必要,节省的空间太有限了,还增加了一些风险(最少有前边提到的逻辑上的风险)。所以,从内存占用分析,这种情况不如直接struct。
不过话说回来,某些情况下虽然不是很节约内存空间,但是union的复用性优势依然存在啊,比如方便多命名,这种“二义性”,从某些方面也可能是优势。这种方法还有个好处,就是某些寄存器或通道大小有限制的情况下,可以分多次搬运。
七、union本质与进阶
根据union固定首地址和union按最大需求开辟一段内存空间两个特征,可以发现,所有表面的定义都是虚的,所谓联合体union,就是在内存给你划了一个足够用的空间,至于你怎么玩~它不管~!(何止是union和struct,C不就是玩地址,所以使用C灵活,也容易犯错)。
union的成员变量是相当于开辟了几个访问途径(即union包含的变量)!但是没开辟的访问方式就不能用了?当然也能用!写个小测试:
#include "stdio.h"union u{int i;double d; //这个union有8字节大小};int main(void){union u uu;uu.i = 10;printf("%d\n",uu.i);char * c;c = (char *)&uu; //把union的首地址赋值、强转成char类型c[0] = 'a';c[1] = 'b';c[2] = 'c'; c[3] = '\0';c[4] = 'd';c[5] = 'e';//最多能到c[7]printf("%s\n",c);//利用结束符'\0'打印字符串"abc"printf("%c %c %c %c %c %c\n",c[0],c[1],c[2],c[3],c[4],c[5]);}
结构体只定义了int和double“接口”,只要获得地址,往里边扔什么数据谁管得到?这就是C语言(不止union)的本质——只管开辟一段空间。
但是获取地址并访问和存取的数据,最好确定是合法(语法)合理(用途符合)的地址,不然虽然能操作,后患无穷,C的头疼之处,可能出了问题都找不到。
八、联合与结构
#include "stdio.h"union number{//定义一个联合int i;struct{//在联合中定义一个结构char first;char second;}half;}num;int main(void){num.i=0x4241; //联合成员赋值printf("%c%c\n", num.half.first, num.half.second);num.half.first='a'; //联合中结构成员赋值num.half.second='b';printf("%x\n", num.i);getchar( );}
输出结果为:
AB
6261
从上例结果可以看出: 当给i赋值后, 其低八位也就是first和second的值; 当给first和second赋字符后, 这两个字符的ASCII码也将作为i 的低八位和高八位。
九、union巧妙地实现多字节数据类型之间的转化
1、音视频编解码算法
经常会涉及一些数据压缩、声音解码、图象的缩放等问题。
这里通过一个例子来推荐一种union绝妙用法(这种方法由Equator公司提供)。在该例子中,利用union结构n64u实现占8个字节n64类型与单字节的c0~c7的相互转换,从而达到数据压缩和分解的目的。
#include#include "stdio.h"#define IN#define OUT#define INOUTtypedefunsigned long longn64;typedef unsigned int n32;typedef unsigned shortn16;typedef unsigned charn8;typedef struct_s8t{unsigned charc0, c1, c2, c3, c4, c5, c6, c7;}s8t;typedefunion{n64n64;struct {n32 l0, l1;}u32;struct {long l0, l1;} s32;struct {unsigned short s0, s1, s2, s3;} u16;struct {short s0, s1, s2, s3;} s16;struct {unsigned char c0, c1, c2, c3, c4, c5, c6, c7;}u8;struct {char c0, c1, c2, c3, c4, c5, c6, c7;} s8;}n64u;#defineMAX_DATA_COMPRESSED_NUM8intcompress64_8(IN constn64* src,IN n16 n,OUT n8* dst);intuncompress8_64(IN const n8* src,IN n16 n,OUT n64* dst);intcompress64_8(IN const n64* src,IN n16 n,OUT n8* dst){n64u n64u_data;registern64* n64ptr=(n64*)src;registern8* n8ptr=dst;n16 i=0,num=n;if(NULL==n64ptr || NULL==n8ptr || n<1){printf("invalid param,src 0x%x,dst 0x%x,n %d\n",n64ptr,n8ptr,n);return 0;}for(i=0;i小于num;i++){n64u_data.n64 = *n64ptr++;*n8ptr++ = (n64u_data.u8.c0+n64u_data.u8.c1+n64u_data.u8.c2+\n64u_data.u8.c3+n64u_data.u8.c4+n64u_data.u8.c5+\n64u_data.u8.c6+n64u_data.u8.c7)/(sizeof(n64)/sizeof(n8));}return 1;}int uncompress8_64(IN const n8* src,IN n16 n,OUT n64* dst){n64u n64u_data;register n64* n64ptr=dst;register n8* n8ptr=(n8*)src;register n8n8data;n16 i=0,num=n;if(NULL==n64ptr || NULL==n8ptr || n<1){printf("invalid param,src 0x%x,dst 0x%x,n %d\n",n64ptr,n8ptr,n);return 0;}for(i=0;i小于num;i++){n8data=*n8ptr++;n64u_data.u8.c0 = n8data;n64u_data.u8.c1 = n8data;n64u_data.u8.c2 = n8data;n64u_data.u8.c3 = n8data;n64u_data.u8.c4 = n8data;n64u_data.u8.c5 = n8data;n64u_data.u8.c6 = n8data;n64u_data.u8.c7 = n8data;*n64ptr++ = n64u_data.n64;}}int main(int argc, char *argv[]){n64n64data[MAX_DATA_COMPRESSED_NUM];n8n8data[MAX_DATA_COMPRESSED_NUM];s8ts8t_data[MAX_DATA_COMPRESSED_NUM]={{1,2,3,4,5,6,7,8},{2,2,3,4,5,6,7,7},{3,2,3,4,5,6,7,6},{4,2,3,4,5,6,7,5},{5,2,3,4,5,6,7,4},{6,2,3,4,5,6,7,3},{7,2,3,4,5,6,7,2},{8,7,6,5,4,3,2,1}};n16 i,n=MAX_DATA_COMPRESSED_NUM;printf("data:\n");for(i=0;i小于n;i++){ n64data[i] = *(n64*)&s8t_data[i]; printf("%3u %3u %3u %3u %3u %3u %3u %3u\n", s8t_data[i].c0,s8t_data[i].c1,s8t_data[i].c2, s8t_data[i].c3,s8t_data[i].c4,s8t_data[i].c5, s8t_data[i].c6,s8t_data[i].c7);}printf("\n");compress64_8(n64data,n,n8data);printf("compressed to:\n");for(i=0;i小于n;i++){ printf("%3u ",n8data[i]);}printf("\n\n");uncompress8_64(n8data,n,n64data);printf("uncompressed to:\n");for(i=0;i小于n;i++){ *(n64*)&s8t_data[i] = n64data[i]; printf("%3u %3u %3u %3u %3u %3u %3u %3u\n", s8t_data[i].c0,s8t_data[i].c1,s8t_data[i].c2, s8t_data[i].c3,s8t_data[i].c4,s8t_data[i].c5, s8t_data[i].c6,s8t_data[i].c7);}printf("\n");}
2、使不同数据包兼容
union的用法如下:
struct _my_struct{ unsigned int struct_id typedef union _my_union { struct my_struct_1; struct my_struct_2; struct my_struct_3; }my_union;}my_struct;
在处理音频视频数据流方面,为了区分音频和视频数据以及来自网络和编解码的数据并减少内存占用使用了下面的数据结构。这种union使用方法在网络应用中特别常见。在数据结构TFrameBufferInfo中,用bufferType标识数据来源。
typedef struct{ intcodecId; intpacketNum; intactualNum; intpacketLen; intleftPacketLen; intframeSample; inttimeStamp; intdataLen; intready; unsigned char*buffer; intreserve1;} TABufferInfoFromCodec;typedef struct{ intcodecId; intbKeyFrame; intpacketNum; intactualNum; intpacketLen; intleftPacketLen; inttimeStamp; intdataLen; intready; unsigned char*buffer; intreserve1;} TVBufferInfoFromCodec;typedef struct{ intcodecId; intbKeyFrame; intpacketNum; intactualNum; intpacketLen;intleftPacketLen; intrtpLen; inttimeStamp;intfirstSquence; intdataLen; intready; unsigned char*buffer;} TVBufferInfoToCodec;typedef struct{ intcodecId; intpacketNum; intactualNum; intpacketLen; intleftPacketLen; intrtpLen; inttimeStamp; intfirstSquence; intdataLen; intready; unsigned char*buffer; intreserve1;} TABufferInfoToCodec;typedef struct{intbufferType;union{ TVBufferInfoFromCodecbufferInfoFromVCoder; TABufferInfoFromCodecbufferInfoFromACoder; TVBufferInfoToCodecbufferInfoFromVNetWork; TABufferInfoToCodecbufferInfoFromANetWork;} buffer_info;} TFrameBufferInfo;int send_to(void* stream);intsend_to(void* stream){}int main(int argc, char *argv[]){TFrameBufferInfo tFrameBufferInfo;TVBufferInfoFromCodec* pVBufferInfoFromCodec;unsigned char buffer[1200*5];tFrameBufferInfo.bufferType=4;pVBufferInfoFromCodec=&tFrameBufferInfo.buffer_info.bufferInfoFromVCoder;pVBufferInfoFromCodec->bKeyFrame= 1;pVBufferInfoFromCodec->packetNum= 2;pVBufferInfoFromCodec->actualNum= 2;pVBufferInfoFromCodec->leftPacketLen= 123;pVBufferInfoFromCodec->dataLen= 1323;pVBufferInfoFromCodec->ready= 1;pVBufferInfoFromCodec->buffer= buffer;send_to((void*)&tFrameBufferInfo);}