联合体(union)的使用方法及其本质

联合体(union)的使用方法及其本质

转自:https://blog.csdn.net/huqinwei987/article/details/23597091

有些基础知识快淡忘了,所以有必要复习一遍,在不借助课本死知识的前提下做些推理判断,温故知新。

1.联合体union的基本特性——和struct的同与不同

union,中文名“联合体、共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。

不过区别也挺明显:

结构体(struct)中所有变量是“共存”的——优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。

而联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间。

2.双刃剑——多种访问内存途径共存

一个例子了然:

//example
#include<stdio.h>
union var{long int l;int i;
};
main(){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呢~

这种逻辑上的错误是很难找出来的(只有当数据类型相去甚远的时候稍好,出个乱码什么的很容易发现错误)。

#include<stdio.h>
union var{long int l;int i;
};
int main(){union var v;v.l = 5;printf("%ld\n",v.l);v.i = 6;
}

3.联合体union和大小端(big-endian、little-endian)

下边示范了一种用途,代表四个含义的四个变量,但是可以用一个int来操作,直接int赋值,无论内存访问(指针大小的整数倍,访问才有效率),还是时间复杂度(一次和四次的区别,而且这四次有三次都是不整齐的地址),都会低一些。

#include<stdio.h>
union var{char c[4];int i;
};int main(){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位linux是小端(little-endian)

4.联合体union所占内存空间大小

前边说了,首先,union的首地址是固定的,那么,union到底总共有多大?根据一些小常识,做个不严谨不高深的基础版验证吧。

根据:分配栈空间的时候内存地址基本上是连续的,至少同类型能保证在一起,连续就说明,我如果弄三个结构体出来,他们三个地址应该连着,看一下三个地址的间隔就知道了。

#include<stdio.h>
union sizeTest{int a;double b;
};
main(){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];double b;
};

打印

the initial address of unionA is 0xbfbb7738
the initial address of unionB is 0xbfbb7760
the initial address of unionC is 0xbfbb7788

88-60=28
60-38=28
算错了?我说的可是16进制0x。那么0x28就是40个字节,正好是数组a的大小。

忘了提一个功能——sizeof()

用sizeof直接看,就知道union的大小了

        printf("the sizeof   of unionA is %d\n",sizeof(unionA));printf("the sizeof   of unionB is %d\n",sizeof(unionB));printf("the sizeof   of unionC is %d\n",sizeof(unionC));printf("the sizeof   of union is %d\n",sizeof(union sizeTest));

上边说的地址规律,没有特定规则,也可能和你的编译器有关。另外,那只是栈空间,还可以主动申请堆空间,当然,堆空间就没有连续不连续一说了。

5.联合体union适用场合

有了前边那个验证,基本可以确认,union的内存是照着里边占地儿最大的那个变量分的。

也就可以大胆的推测一下,这种union的使用场合,是各数据类型各变量占用空间差不多并且对各变量同时使用要求不高的场合(单从内存使用上,我觉得没错)。

像上边做的第二个测试,一个数组(或者更大的数组int a[100]),和一个或者几个小变量写在一个union里,实在没什么必要,节省的空间太有限了,还增加了一些风险(最少有前边提到的逻辑上的风险)。所以,从内存占用分析,这种情况不如直接struct。

不过话说回来,某些情况下虽然不是很节约内存空间,但是union的复用性优势依然存在啊,比如方便多命名,这种“二义性”,从某些方面也可能是优势。这种方法还有个好处,就是某些寄存器或通道大小有限制的情况下,可以分多次搬运。

6.本质&进阶

根据union固定首地址union按最大需求开辟一段内存空间两个特征,可以发现,所有表面的定义都是虚的,所谓联合体union,就是在内存给你划了一个足够用的空间,至于你怎么玩它不管!(何止是union和struct,C不就是玩地址么,所以使用C灵活,也容易犯错)

没错,union的成员变量是相当于开辟了几个访问途径(即union包含的变量)!但是,没开辟的访问方式就不能用了?当然也能用!

写个小测试:

#include<stdio.h>
union u{int i;double d;//这个union有8字节大小
};
main(){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的头疼之处,可能出了问题你都找不到。

补充:

补充1:

解决一下捧场网友的困惑。

关于“有名”与“无名”联合体在结构体内所占空间的问题,其实这和是不是结构体无关,只和“有名”、“无名”有关,而且有名无名也是表象,其实是声明类型与定义变量的区别,看例子,直接打印,

#include <stdio.h>
struct s1{union u{int i;};struct ss1{int i;};
};struct s2{union{int i;};struct{int i2;};
};struct s3{//the same to s2union su3{int i;}su33;struct ss3{int i;}ss33;
};union su4{int i;
};
struct ss4{int i;
};
struct s4{//the same to s3union su4 su44;struct ss4 ss44;
};
struct s5{//the same to s1union su4;struct ss4;
};struct s6{//the same to s1union{int;};struct{int;};
};main(){struct s1 sVal1;struct s2 sVal2;struct s3 sVal3;struct s4 sVal4;struct s5 sVal5;struct s6 sVal6;printf("sVal1's size:%d\n",sizeof(sVal1));printf("sVal1:%p\t%d\n",&sVal1,sVal1);printf("sVal2's size:%d\n",sizeof(sVal2));printf("sVal2:%p\t%d\n",&sVal2,sVal2);printf("sVal3's size:%d\n",sizeof(sVal3));printf("sVal3:%p\t%d\n",&sVal3,sVal3);printf("sVal4's size:%d\n",sizeof(sVal4));printf("sVal4:%p\t%d\n",&sVal4,sVal4);printf("sVal5's size:%d\n",sizeof(sVal5));printf("sVal5:%p\t%d\n",&sVal5,sVal5);printf("sVal6's size:%d\n",sizeof(sVal6));printf("sVal6:%p\t%d\n",&sVal6,sVal6);
}

地址供参考,主要看size,分别为:0,8,8,8,0,0

s1只有类型声明,没有定义,没有变量自然就没有空间占用(s5同)。

s2这种写法就是直接定了union和struct。

s3和s2的区别,只是s2过于简化,s3的意思是既声明了union su3,又定义了这个类型对应的变量su33.

s4和s5作为对比,为了更好的说明这一点。s5也是纯“贴”表达式,没声明变量。

s6乍一看类似s2,其实union内部没有具体变量,也是为了做对比的。和s1的不同之处是,一个是外部的union,一个是内部的int,都是干声明不定义,所以没成员,不占用空间。

类型就是类型,和是不是结构体、联合体无关的,你的“int i;”中i不就是个变量吗?如果换成int;结果相同(这就是s6)。

另外,这种做法编译的时候GCC会给你在相应的行做出提示“union_with_name.c:49: 警告:没有声明任何东西”

很多人表示打印结果不一样,我试过很多次,不一样的环境,都是一样的。

以上仅属于个人心得和推测,重点在于学习思维和推理验证过程,不保证正确性与权威性。有兴趣讨论或者有发现错误的,欢迎留言交流指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/532792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux设备驱动之串口移植,Linux设备驱动之UART驱动结构

一、对于串口驱动Linux系统中UART驱动属于终端设备驱动&#xff0c;应该说是实现串口驱动和终端驱动来实现串口终端设备的驱动。要了解串口终端的驱动在Linux系统的结构就先要了解终端设备驱动在Linux系统中的结构体系&#xff0c;一方面自己了解的不够&#xff0c;另一发面关于…

linux python复制安装,复制一个Python全部环境到另一个环境,python另一个,导出此环境下安装的包...

复制一个Python全部环境到另一个环境&#xff0c;python另一个,导出此环境下安装的包导出此环境下安装的包的版本信息清单pipfreeze>requirements.txt联网&#xff0c;下载清单中的包到all-packet文件夹[[email protected] ~]# pip download -d ./all-packet -r requirement…

NVIDIA英伟达的Multi-GPU多卡通信框架NCCL

NVIDIA英伟达的Multi-GPU多卡通信框架NCCL 笔者注&#xff1a;NCCL 开源项目地址&#xff1a;https://github.com/NVIDIA/nccl 转自&#xff1a;https://www.zhihu.com/question/63219175/answer/206697974 NCCL是Nvidia Collective multi-GPU Communication Library的简称&…

C语言n个坐标点间的最大距离,c语言已知两点坐标,求另一点到穿过这两点的直线最短距离。...

c语言已知两点坐标&#xff0c;求另一点到穿过这两点的直线最短距离。以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;c语言已知两点坐标&#xff0c;求另一点到穿过这两点的直线最短距离。#…

[分布式训练] 单机多卡的正确打开方式:理论基础

[分布式训练] 单机多卡的正确打开方式&#xff1a;理论基础 转自&#xff1a;https://fyubang.com/2019/07/08/distributed-training/ 瓦砾由于最近bert-large用的比较多&#xff0c;踩了很多分布式训练的坑&#xff0c;加上在TensorFlow和PyTorch之间更换&#xff0c;算是熟…

s3c2416开发板 linux,S3C2416移植内核Linux3.1的wm9713声卡过程

移植内核的声卡驱动。原因没有声卡驱动&#xff0c;WM9713声卡驱动移植(原来的内核有UDA1341声卡驱动&#xff0c;我们再次基础上直接修改)1、直接复制内核得到三个文件:s3c2416_wm9713.c , wm9713.c , s3c2416_ac97.c.linux-3.1\sound\soc\codecs\Wm9713.c---->wm9713.c;li…

Linux查看文件内容命令:cat, tail, head, more, less

Linux查看文件内容命令&#xff1a;cat, tail, head, more, less cat 直接显示整个文件。 cat直接显示全部文件内容&#xff0c;没有换页等交互。 cat filenamemore more命令&#xff0c;功能类似 cat &#xff0c;cat命令是整个文件的内容从上到下显示在屏幕上。 more会…

linux查看队列 msg,linux第10天 msg消息队列

cat /proc/sys/kernel/msgmax最大消息长度限制cat /proc/sys/kernel/msgmnb消息队列总的字节数cat /proc/sys/kernel/msgmni消息条目数消息队列综合案例//server#include #include #include #include #include #include #include #include #define ERR_EXIT(m)do{perror(m);}wh…

Linux中 C++ main函数参数argc和argv含义及用法

Linux中 C main函数参数argc和argv含义及用法 简介 argc 是 argument count的缩写&#xff0c;表示传入main函数的参数个数&#xff1b; argv 是 argument vector的缩写&#xff0c;表示传入main函数的参数序列或指针&#xff0c;并且第一个参数argv[0]一定是程序的名称&…

c语言六位抢答器课程设计,51单片机八路抢答器课程设计

;说明&#xff1a;本人的这个设计改进后解决了前一个版本中1号抢答优先的问题&#xff0c;并增加了锦囊的设置&#xff0c;当参赛选手在回答问题时要求使用锦囊&#xff0c;则主持人按下抢答开始键&#xff0c;计时重新开始。;八路抢答器电路请看下图是用ps仿真的&#xff0c;已…

ELF文件详解—初步认识

ELF文件详解—初步认识 转自&#xff1a;https://blog.csdn.net/daide2012/article/details/73065204 一、 引言 在讲解ELF文件格式之前&#xff0c;我们来回顾一下&#xff0c;一个用C语言编写的高级语言程序是从编写到打包、再到编译执行的基本过程&#xff0c;我们知道在C…

埃及分数问题c语言,埃及分数问题(转)

今日&#xff0c;小雨和小明来到网络中心&#xff0c;继续与刘老师讨论“数的认识”问题。刘老师说&#xff1a;“还有一种‘埃及分数’需要认识。这是一类分裂分数的思维题&#xff0c;对思维能力的训练很有价值。”小明说&#xff1a;“有意思&#xff0c;愿洗耳恭听。”刘老…

linux常用命令--开发调试篇

前言 Linux常用命令中有一些命令可以在开发或调试过程中起到很好的帮助作用&#xff0c;有些可以帮助了解或优化我们的程序&#xff0c;有些可以帮我们定位疑难问题。本文将简单介绍一下这些命令。 转自&#xff1a;https://www.yanbinghu.com/2018/09/26/61877.html 示例程序…

简单有趣的c语言小程序,一个有趣的小程序

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼源码:#include #include #include #include #include HINSTANCE g_hInstance 0;LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR lpCmdLine,int nSh…

linux下ora 01110,ORA-01003ORA-01110

Oracle 9i数据库登录时&#xff0c;提示ORA-01003&ORA-01110&#xff0c;大概意思是数据文件存储介质损坏。startup nomount,正常&#xff1b;alter database mount,也正常&#xff1b;alter database open,提示如下&#xff1a;alter database open*ERROR 位于第 1 行:ORA…

x11转发:通过ssh远程使用GUI程序

x11转发&#xff1a;通过ssh远程使用GUI程序 我们常常使用ssh服务远程操控服务器&#xff0c;大多数操作我们都可以通过命令行命令来实现。 ssh远程无法查看GUI程序 现在&#xff0c;笔者在x11-test目录下放入一张图片test.jpg&#xff0c;并通过opnencv-python写一个简单的…

操作系统引导详细过程

操作系统引导详细过程 转自&#xff1a;https://blog.csdn.net/lijie45655/article/details/89366372 就直观而言&#xff0c;我们所见到计算机启动的过程是&#xff1a;按下电脑开机键&#xff0c;系统在黑色的屏幕下打印出一些英文语句、然后进入进度条状态&#xff0c;最后…

android 自定义透明 等待 dialog,Android自定义Dialog内部透明、外部遮罩效果

Android自定义Dialog内部透明、外部遮罩效果发布时间&#xff1a;2020-09-09 03:01:41来源&#xff1a;脚本之家阅读&#xff1a;117作者&#xff1a;zst1303939801本文实例为大家分享了Android自定义Dialog遮罩效果的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下图…

对比损失的PyTorch实现详解

对比损失的PyTorch实现详解 本文以SiT代码中对比损失的实现为例作介绍。 论文&#xff1a;https://arxiv.org/abs/2104.03602 代码&#xff1a;https://github.com/Sara-Ahmed/SiT 对比损失简介 作为一种经典的自监督损失&#xff0c;对比损失就是对一张原图像做不同的图像…

android 融云浏览大图,融云 Android sdk kit 头像昵称更新机制

先申明笔者的实现方式不是唯一 也不一定是最优化的方案 如果您看到此篇博文 有不同看法 或者 更好的优化 更高的效率 欢迎在评论发表意见 融云官网点我融云头像机制相关视频详解首先跟大家说一下 kit 跟 lib 的头像机制 kit 是已经包含融云已经给开发者定制好的界面 诸如 会话界…