由union引发的Struct占用内存空间和大小端问题的思考

1. 背景

  • 在看Lua源码的时候,很多地方都用到了union(共用体或者联合体),在定义lua类型的时候,为了以一个结构来包含所有的数据类型,设计了一个 TValue类型,TValue类型最终关联到 Value类型,而这个Value类型就是一个union。如下图:

  • 在这里插入图片描述

  • 可以看出,基础数据类型(除了string)之后都包含了。

  • 联合体允许定义任意一种数据类型,这些数据共享同一段内存,以达到节省空间的目的。union变量所占用的内存长度等于最长的成员的内存长度。这个联合体内的数据只能取其一(俗称 “n选1”)。

  • 问题来了,联合体占用的内存大小是怎么算的?和struct占用内存的大小算法区别在哪里?

2. Struct占用内存大小的算法

  • 前置

    • 对于32位CPU,CPU每次都是以 4 的倍数来读取内存地址的。64位CPU以 8 的倍数来读取内存地址。(16位以 2 的倍数来读取内存地址)
    • 一个内存地址存储的是一个字节(1 byte),即 8 位(8 bit)。
    • Struct在内存中存储的时候要做 字节对齐
  • Struct在内存中的存放规则

    • 结构体成员的首地址(开始地址)必须要是这个成员所占空间 的整数倍。
    • 结构体占用空间的总大小必须要是 占用空间最大成员所占空间的整数倍。
    • 结构体的首地址必须要是 占用空间最大成员所占空间的整数倍。
  • 对上面规则的解释

    • 第一个就是字节对齐。比如一个 int 变量 在32位系统上占4个字节,double占8个字节,那么这个变量的存放地址就必须要是4/8的整数倍。
    • 第二个就是计算完最后一个成员地址后,要看总大小是不是 占用地址最大成员 所占空间的整数倍。
    • 第三个因为预设结构体首地址是0,所以无论最大成员所占空间数是多少,肯定都是整数倍。
  • 看下面的例子:

  • 在这里插入图片描述

    • 上图右侧的例子计算步骤如下:
      1. char a1占1个字节,首地址是 0,0%1==0(规则第一条),所以不需要填充,已分配大小:1字节
      2. Int b1 占4个字节,首地址是1,1%4 !=0 (不满足第一条,这个成员的首地址不是占用空间(4字节)的倍数),所以需要填充3,已分配大小:8字节
      3. char c1占1个字节,首地址是8,8%1==0(满足规则第一条),所以不需要填充,已分配大小:9字节
      4. 最后一个成员计算完毕,已分配9字节,最大成员占用空间是4(int b1),但是 9%4!=0(不满足第二条 —总大小是最大成员空间的整数倍),所以需要填充3。总大小:12字节
  • 看一个稍微复杂一点的例子,包含了嵌套。

    • 在这里插入图片描述

在这里插入图片描述
- 如上图,Example4里面包含了 struct Example2。根据规则, struct Example2 占用了24个字节(计算步骤略)。
- 重点看一下 Example4的计算步骤:
1) char a 占 1 个字节。首地址是0,0%10,无需填充,已分配大小:1字节
2) struct Example2 占24个字节,首地址是1,struct Example2 的最大成员
所占空间是 8 (规则第二条),1%8 != 0,需要填充7个字节(注意:不是23个字节),已分配大小:32字节
3) int c 占4个字节,首地址是32,32%4 == 0,无需填充,已分配大小:36字节
4) double d 占8个字节,首地址36,36%8 != 0,需要填充4,已分配大小:48字节。
5) 最后一个成员计算完毕,已分配48字节,最大成员占用空间是8(注意:这里不是 sizeof(struct Example2)=24),48%8
0,无需填充,总大小:48字节

3. Union内存大小的算法

  • 啰嗦一遍 union 的定义:叫 共用体,也叫联合体,在里面可以定义多种不同的数据类型,这些数据可以共享同一段内存,以达到节省空间的目的。union定义的变量所占用的内存长度等于最长的成员的内存长度
  • 看一个简单的例子:
  • 在这里插入图片描述
    • sizeof(union test)的结果为4。 Char mark,long num,float score这三个变量存放在同一个地址开始的内存单元中。三个变量所占用的字节数是不一样的,也就是说,三个变量相互覆盖。如下示意图:
    • 在这里插入图片描述
      • 如上图,三个变量的地址都是从 0x0000开始,也就是从0开始,到0x0003结束(红色条标注),占用 4 个字节。
  • 看一个稍微复杂一点的例子:
  • 在这里插入图片描述
    • 上图中,myun里包含两个变量,一个是struct u(占12个字节),一个是int k(4个字节),所以sizeof(union myun)的结果是12。
    • 同时,由于union 是共享内存的,所以strtuct u的起始地址是 0,结束地址是11,int k的起始地址是0,结束地址是3。
    • 在main里面,设置了struct u的 x,y,z分别是4,5,6。设置了int k 是 0。所以内存里面的变化是这样的(如下图):
      • 在这里插入图片描述
      • 如上图,可以看出main里面对union myun赋值的过程。后面对 k 的赋值也是从首地址开始,会把原来设置的 4 覆盖掉。所以上图打印的结果应该是 0,5,6

4. 大小端的问题

  • 既然union里面的变量共享内存,都是从某一个内存地址开始,那么就可以利用这种特性来测试CPU是大端模式还是小端模式。
  • 主要思想是:定义一个union变量,设置这个union变量里面的某一个成员的值,然后看union里面char类型的变量的值是不是1,如果是1则是小端。否则是大端。
  • 实现如下:
    • 在这里插入图片描述
    • 上图定义了一个 union MyUnion,通过设置union里面的成员变量 int a 之后,在去查看 char c 变量的值,如果是小端(从低地址到高地址依次存放数据的低位字节到高位字节),则c的值应该是1。如果是大端(从低地址到高地址依次存放数据的高位字节到低位字节),则c的值肯定不等于1。
    • 如下图,看看变量在内存里分别对应大小端的情况
      • 在这里插入图片描述
      • 上图中 0X1234abcd,分别以大小端存储时的异同。可以看出对于一个变量的值 0X1234abcd,从高位字节到低位依次是 1234abcd,如果是大端,则低地址放高位字节,高地址放低位字节,则排列顺序如上图。
        • 这里面有一个小小的问题,可能有的人会问:为什么 0X1234ABCD 在内存中会以 0X12,0X34,0XAB,0XCD这样去存放?为什么不是 0X1, 0X2, 0X3,0X4,0XA,0XB,0XC,0XD这样的方式去存放?
        • 解释上面的问题
          • 首先 0X1234ABCD 是一个16进制的数值。这个16进制的数值转换为2进制是 0001 0010 0011 0100 1010 1011 1100 1101,可以看出,16进制的这个数的每一位对应 4 个bit(因为是16进制,2的4次方,所以一个16进制的位转换为2进制就是 4 bit)。
          • 其次,需要知道的是,一个内存地址对应一个内存空间,上图中的 0X0000 号内存地址就是一个内存空间,而一个内存空间存储一个字节,一个字节有8bit,所以一个内存地址存放的是2个16进制位(一个16进制位占4bit,2个就是 2 * 4bit = 8bit),所以一个内存地址里面存放的是 0X12,0x34等等,而不是 0x1,0x2。
      • 回到上图的解释中,如果是大端,则低地址存放高字节,高地址存放低字节,所以此时从低地址到高地址依次存放的是 0x12,0x34,0xab,0xcd。而如果是小端,则低地址存放低字节,高地址存放高字节,所以从低地址到高地址依次存放的是 0xcd,0xab,0x34,0x12。
      • 看看内存里实际对变量是如何存储的,如下图:
        • 在这里插入图片描述
        • 看代码里面定义的变量 TestNum1 = 0X1234abcd,在内存里面这个变量的起始地址是 0x0000001869379270,存放的顺序是 cd,ab,34,12。结束地址应该是 0x0000001869379273 。(由这个图我们还能看出,这里是 little endian“小端”)
        • 然后我们再看蓝色箭头的内容, TestNum2 = 0X00000001,这个变量的起始地址是 0x0000001869379274,存放顺序是 01,00,00,00。这里我们能得出几个信息,首先,显然 TestNum1 和 TestNum2 都占了 4 个字节(对应 int 占4个字节)。其次,这两个变量在内存中的存放方式表明是== little endian模式==。

5. Reference

  • https://developer.aliyun.com/article/369914 c++中union的使用,看高手们如何解释的
  • https://eysent.com/others/struct-padding/【C】如何计算结构体占用内存大小
  • https://blog.csdn.net/HJS020828/article/details/123729229 结构体在内存中的存储

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

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

相关文章

二、C++项目:仿muduo库实现并发服务器之时间轮的设计

文章目录 一、为什么要设计时间轮?(一)简单的秒级定时任务实现:(二)Linux提供给我们的定时器:1.原型2.例子 二、时间轮(一)思想(一)代码 一、为什…

【C++】C++的IO流

C的IO流 一、C语言的输入与输出二、流是什么三、CIO流1、C标准IO流2、C文件IO流3、stringstream的简单介绍 一、C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。p…

raw图片处理软件:DxO PhotoLab 6 mac中文版支持相机格式

DxO PhotoLab 6 mac是一款专业的RAW图片处理软件,适用于Mac操作系统。它具有先进的图像处理技术和直观易用的界面,可帮助用户轻松地将RAW格式的照片转换为高质量的JPEG或TIFF图像。 DxO PhotoLab 6支持多种相机品牌的RAW格式,包括佳能、尼康、…

Rust之自动化测试(二):控制测试如何运行

开发环境 Windows 10Rust 1.72.1 VS Code 1.82.2 项目工程 这里继续沿用上次工程rust-demo 控制测试如何运行 正如cargo run编译您的代码,然后运行生成的二进制文件一样,cargo test在测试模式下编译您的代码,然后运行生成的测试二进制文件…

马尔萨斯《人口原理》读后

200 多年前的书,很多人都说旧的东西过时了,但我觉得它只是被修正了,内核并不过时。毕竟,静态存量分析这本身就不符合现实,用现在的话说,建模就错了,但马尔萨斯的理论核心并不仅仅是一个模型&…

华为多路径软件UltraPath

检查多路径是否安装。 # rpm -qa|grep UltraPath 查看UltraPath软件版本 # upadmin show version 查看物理路径状态。 #upadmin show path 查看虚拟磁盘信息。 #upadmin show vlun 查看逻辑路径状态。 #upadmin show vlun 查看多路径配置。 #upadmin show upconfig 卸载Ul…

brew 安装MySQL 5.7

写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成…

php导出cvs,excel打开数字超过16变科学计数法

今天使用php导出cvs,在excel中打开,某一个字段是数字,长度高于16位结果就显示科学计数法 超过15位的话从第16位开始就用0代替了 查询了半天总算解决了就是在后面加上"\t" $data[$key][1] " ".$value[1]."\t";…

你的游戏项目有这些问题吗?

在移动游戏对高品质画面的要求不断增加的背景下,我们一直专注于移动设备GPU性能的优化,以确保您的游戏体验得以最佳展现。然而,不同GPU芯片之间的性能差异以及由此可能引发的GPU瓶颈问题使得优化工作更加具有挑战性。 因此,在不久…

Spring Boot中配置文件介绍及其使用教程

目录 一、配置文件介绍 二、配置简单数据 三、配置对象数据 四、配置集合数据 五、读取配置文件数据 六、占位符的使用 一、配置文件介绍 SpringBoot项目中,大部分配置都有默认值,但如果想替换默认配置的话,就可以使用application.prop…

Spring结合自定义注解实现 AOP 切面功能

Spring结合自定义注解实现 AOP 切面功能 Spring AOP 注解概述Aspect 快速入门execution 切点表达式 拦截指定类的方法Pointcut("annotation(xx)") 拦截拥有指定注解的方法环绕通知 实现开关目标方法案例1:自定义注解切面实现统一日志处理1.自定义日志注解…

uni-app:获取元素宽高

效果 代码 这里我定义的宽为500px,高为200排序,控制台输出的结果是502,202。原因是我设置了上下左右宽度各为1px的border边框导致 核心代码分析 // const query uni.createSelectorQuery();表示创建了一个选择器查询实例。通过这个实例,你可以使用不同的方法来选择…

英语——分享篇——每日100词——501-600

hill——will愿意——他不愿意去小山里 Easter——east东方(熟词)er儿(拼音)——东方的儿子都过复活节 exhibition——ex前夫(熟词)hi嗨(熟词)bition比神(谐音)——展览会上前夫很嗨,比神还开心 chase——vt.追捕,追逐,追赶——cha茶se色——…

【C++】vector的介绍 | 常见接口的使用

目录 vector的介绍 常见接口 构造函数 尾插push_back() vector的遍历 1.用方括号下标 遍历: 2.调用at()来访问: 3.用迭代器遍历: 4.范围for遍历: vector空间 vector增删查改 覆盖assign() 查找find() 插入insert() …

Java on Azure Tooling 8月更新|以应用程序为中心的视图支持及 Azure 应用服务部署状态改进

作者:Jialuo Gan - Program Manager, Developer Division at Microsoft 排版:Alan Wang 大家好,欢迎阅读 Java on Azure 工具的八月更新。在本次更新中,我们将推出新的以应用程序为中心的视图支持,帮助开发人员在一个项…

Spring修炼之路(1)基础入门

一、简介 1.1Spring概述 Spring框架是一个轻量级的Java开发框架,它提供了一系列底层容器和基础设施,并可以和大量常用的开源框架无缝集成,可以说是开发Java EE应用程序的必备。Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器&…

【面试经典 150 | 滑动窗口】串联所有单词的子串

文章目录 写在前面Tag题目来源题目解读解题思路方法一:两个哈希表方法二:滑动窗口 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更…… 专栏内容以分析题目为主,并附带一…

JS三大运行时全面对比:Node.js vs Bun vs Deno

全文约 5100 字,预计阅读需要 15 分钟。 JavaScript 运行时是指执行 JavaScript 代码的环境。目前,JavaScript 生态中有三大运行时:Node.js、Bun、Deno。老牌运行时 Node.js 的霸主地位正受到 Deno 和 Bun 的挑战,下面就来看看这…

计算机视觉与深度学习-循环神经网络与注意力机制-RNN(Recurrent Neural Network)、LSTM-【北邮鲁鹏】

目录 举例应用槽填充(Slot Filling)解决思路方案使用前馈神经网络输入1-of-N encoding(One-hot)(独热编码) 输出 问题 循环神经网络(Recurrent Neural Network,RNN)定义如何工作学习目标深度Elm…

Vue中自定义实现类似el-table的表格效果实现行颜色根据数据去变化展示

主要使用div布局实现表格效果&#xff0c;并使用渐变实现行背景渐变的效果 页面布局 <div class"table-wrap"><div class"table-title"><divv-for"(item, index) in tableColumn":key"index":prop"item.prop&qu…