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

news2024/11/19 1:47:48

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.coloradmin.cn/o/1032317.html

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

相关文章

番茄小说推文和番茄短剧推广怎么申请授权

番茄小说推文和番茄短剧推广都可以通过“巨量推文”进行授权 番茄小说是一个独立的小说平台,他们在推文市场有推广预算后交给我们达人进行推广,推广完成后可以获得番茄小说的cpa拉新奖励 番茄短剧和番茄小说类似,只是一个是推广短剧的平台一…

VisualStudio配置opencv

下载opencv 链接:https://opencv.org/releases/ 我下载的是4.7.0,选择windows下载。 下载成功后打开exe文件,选择路径安装。 配置环境变量 安装成功后找到安装目录,复制bin目录路径。 我的是放在了D盘 D:\Opencv4.7.0\opencv…

多模态 多引擎 超融合 新生态!2023亚信科技AntDB数据库8.0产品发布

9月20日,以“多模态 多引擎 超融合 新生态”为主题的亚信科技AntDB数据库8.0产品发布会成功举办,从技术和生态两个角度全方位展示了AntDB数据库第8次大型能力升级和生态建设成果。浙江移动、用友、麒麟软件、华录高诚、金云智联等行业伙伴及业界专家共同…

《动手学深度学习 Pytorch版》 7.5 批量规范化

7.5.1 训练深层网络 训练神经网络的实际问题: 数据预处理的方式会对最终结果产生巨大影响。 训练时,多层感知机的中间层变量可能具有更广的变化范围。 更深层的网络很复杂容易过拟合。 批量规范化对小批量的大小有要求,只有批量大小足够…

CTF--攻防世界--杂项基础

多做几道基础题就会发现这东西真的跟智障题一样,开始嘲笑当初的自己了。 就当是学习笔记了【根据题库给的顺序,随便写几道】 用那个隐写工具一步完事 这一题我是真的理解了杂项的概念,这玩意是真杂啊,死活都没想到居然还有这种题…

网络编程day05(IO多路复用)

今日任务&#xff1a; TCP多路复用的客户端、服务端&#xff1a; 服务端代码&#xff1a; #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> …

若依使用及源码解析(前后端分离版)

部署环境 JDK > 1.8 MYSQL > 5.7 Maven > 3.0 Node > 12 Redis > 3 运行若依项目 下载若依源码 若依官网 若依项目源码(前后端分离) 运行后端项目 ruoyi-ui就是vue项目&#xff08;这里使用vscode打开&#xff09; 整体用idea打开 1.配置数据库(sq…

千兆以太网传输层 UDP 协议原理与 FPGA 实现

文章目录 前言心得体会一、UDP 协议介绍二、UDP 数据报格式三、UDP 数据发送测试四、Verilog实现UDP 数据发送1、IP 头部检验 IPchecksun 的计算2、以太网报文的校验字段 FCS 的计算3、以太网报文发送模块实现五、以太网数据发送测试六、仿真代码七、仿真波形展示八、上板测试九…

婚礼策划展示小程序制作全程解析

随着互联网的发展&#xff0c;小程序已成为各行各业所钟爱的一种数字化工具。对于婚礼策划师来说&#xff0c;一款专为自己业务打造的小程序能够更好地展示婚礼策划方案&#xff0c;提升服务质量&#xff0c;加强与客户的沟通。以下就是制作婚礼策划展示小程序的全程解析。 一、…

【Linux网络编程】gdb调试技巧

这篇博客主要要记录一下自己在Linux操作系统Ubuntu下使用gbd调试程序的一些指令&#xff0c;以及使用过程中的一些心得。 使用方法 可以使用如下代码 gcc -g test.c -o test 或者 gcc test.c -o test ​ -g的选项最好添加&#xff0c;如果不添加&#xff0c;l指令无法被识别 …

机试(2017 cs se)

2017计算机系夏令营 题解参考&#xff1a; 2017 华东师范计算机系暑期夏令营机考 A. 不等式 Problem #3304 - ECNU Online Judge 有点像贪心算法 选一个刚刚好在条件范围里的b[i]作为候选&#xff0c;【这个“刚刚好”是指选一个符合这个条件的最极限的值】 代码 #in…

力扣669 补9.16

最近大三上四天有早八&#xff0c;真的是受不了了啊&#xff0c;欧嗨呦&#xff0c;早上困如狗&#xff0c;然后&#xff0c;下午困如狗&#xff0c;然后晚上困如狗&#xff0c;尤其我最近在晚上7点到10点这个时间段看力扣&#xff0c;看得我昏昏欲睡&#xff0c;不自觉就睡了1…

关于Python安装Scrapy库的常见报错解决

目录 1、关于pip3命令的报错 2、执行scrapy报错&#xff08;Python3下的OpenSSL模块出错&#xff09; 3、卸载pyopenssl时报错 由于Scrapy该库在Windows下会存在兼容问题&#xff0c;下面介绍的是在Linux系统进行安装。 1、关于pip3命令的报错 报错代码&#xff1a; error…

redis漏洞修复:(CNVD-2019-21763)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、漏洞内容二、镜像准备1.确认镜像版本2.下载镜像 三、配置文件准备1.获取配置文件2.修改配置文件 四、启动redis容器五、修改iptables文件总结 前言 漏扫发…

Java JVM分析利器JProfiler 结合IDEA使用详细教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、JProfiler是什么&#xff1f;二、我的环境三、安装步骤1.Idea安装JProfiler插件1.下载程序的安装包 四、启动 前言 对于我们Java程序员而言&#xff0c;肯…

博通强迫三星签不平等长约,被韩处罚1亿元 | 百能云芯

近日&#xff0c;博通&#xff08;Broadcom&#xff09;这家国际知名的半导体公司因其市场主导地位的滥用&#xff0c;遭到了韩国公平贸易委员会&#xff08;FTC&#xff09;的严厉制裁&#xff0c;罚款高达191亿韩元&#xff0c;约合人民币1.04亿元。这一惩罚背后的故事揭示了…

用AVR128单片机的音乐门铃

一、系统方案 1、使用按键控制蜂鸣器模拟发出“叮咚”的门铃声。 2、“叮”声对应声音频率714Hz&#xff0c;“咚”对应声音频率500Hz,这两种频率由ATmega128的定时器生成&#xff0c;定时器使用的工作模式自定&#xff0c;处理器使用内部4M时钟。“叮”声持续时间300ms&#x…

Linux新手教程||Linux vi/vim

所有的 Unix Like 系统都会内建 vi 文书编辑器&#xff0c;其他的文书编辑器则不一定会存在。 但是目前我们使用比较多的是 vim 编辑器。 vim 具有程序编辑的能力&#xff0c;可以主动的以字体颜色辨别语法的正确性&#xff0c;方便程序设计。 什么是 vim&#xff1f; Vim是…

链表oj3(Leetcode)——相交链表;环形链表

一&#xff0c;相交链表 相交链表&#xff08;Leetcode&#xff09; 1.1分析 看到这个我们首先想到的就是一个一个比较他们的值有相等的就是交点&#xff0c;但是如果a1和b2的值就相等呢&#xff1f;所以这个思路不行&#xff0c;第二种就是依次比较链表&#xff0c;但是这…

xdebug3开启profile和trace

【xdebug开启profiler】 https://xdebug.org/docs/profiler http://www.xdebug.org.cn/docs/profiler 1、php.ini添加下面配置然后重启php容器&#xff1a; xdebug.modeprofile ;这个目录保存profile和trace文件 xdebug.output_dir /var/tmp/xdebugPHP日志提示报错&#xff1a…