首页 > 系统相关 >数据在内存中的储存详解

数据在内存中的储存详解

时间:2024-03-30 16:04:35浏览次数:17  
标签:储存 二进制 浮点数 补码 详解 内存 我们

我们在使用某一个变量时有没有想过这个变量在内存中是如何储存的呢?是我们输入一个十进制的值,内存中就直接储存这个十进制的值,还是别的内容呢?

1.整数在内存中的存储

我们首先说结论,那就是整数在内存中是以一个二进制补码的方式来存储的。

我们以整形int为例,一个整形是4个字节,一个字节是8个比特位,那么一个整形就是32个比特位。

整形在内存中就是以这32个比特位来储存的。对于正整数来说其实就是把一个整数化为二进制,然后前面补0补够32位就行。

我们就以整形4为例,它的二进制是 100,那我们只需要在前面补0就行

000000000000000000000000000000100

那可能会有人说这是正整数,那负数怎么表示,要是直接换成二进制的话那个负号该怎么表示,难道内存中还会储存负号?

内存中当然是不会储存负号的,但是也有办法来表示负数,对于一个有符号整形,规定这个整形的二进制序列的最高位是符号位。

一个整形不是32个比特位,最前面的就是他的符号位,符号位是0就是正数,为1就是负数。

我们刚刚说正整数在内存中的存储就是把十进制的数字换为二进制然后前面补0就行,但是对于负数就不是那么简单了。

这里就涉及3个概念,二进制的原码,反码和补码

原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码+1就是补码。

负数在内存中存储的就是补码,我们以-2为例

//原码
10000000000000000000000000000010
//反码 除符号位以外按位取反
11111111111111111111111111111101
//补码 反码 + 1
11111111111111111111111111111110

其实也很简单,反码实际上对我们现在来说是没什么用的,我们只需要记住原码除符号位以为取反+1就是补码就行,同理其实补码取反+1其实就是原码。当然这些规则是对负数来说的,对于正数来说原码反码补码都相同。

到这里我们就可以下个结论了,整数在内存中是按照补码来储存的,无论是正整数还是负整数。在进行运算时也是用补码来算的。

可能会有人疑惑为什么会要储存补码,而不是直接储存原码,要搞这么麻烦。实际上这是为了统一运算,把减法转为加法,因为CPU中只有加法器,只能实现加法,对于减法就无能为力,所以引入补码就是对实施减法时,将其转换为加上一个负数来实现,两个数的相加实际上是补码相加,这样就可以来实现减法。

2.二进制与十六进制的转换与内存窗口

这里我是用的VS2022进行的操作,别的可能不同

可能有些同学可能会想亲眼看看某个数在内存中到底是怎么储存的,这也是有方法的,先按f10或f11调试起来,这两个键都是让程序向下运行一行,具体差别就要去专门研究研究,这里就不展开讲。

e18720cc70a74ec1b43c3cb0262a15a6.png

要注意的是要想看到某个变量的值,要先让程序运行过创建变量所在的那一行。

成功打开内存窗口后,我们可以是两个数字或者字母组成一对,这实际上是一个字节,但是列数太多了,我们点击右上角有个自动,然后调成4列,一个整形是四个字节,四列就正好是四个字节,一个整形。

59ababf22059464293162e2bb4307396.png

但是我们刚刚学习的储存的是二进制序列,而二进制序列是一个个比特位啊,八个比特位是一个字节,所以要想判断是不是对的,还要将二进制序列转换为十六进制序列。

那么我们来学习一下二进制与十六进制的转换。

其实也很简单,就是四个二进制位转换为一个十六进制位,我们就以1来看

3.大小端储存与判断

3.1大小端储存

假设我有一个变量a,值是0x11223344,0x表示十六进制,我这里直接把它的值设置为一个十六进制的值是方便接下来观察。

按照我们的最本能的想法,它在内存中应该是这样储存的 11 22 33 44,但是如果我们在内存窗口中看它是怎么储存的,会发现是这样的 44 33 22 11,如果我们观察过前面的地址编号,就会发现它的地址是从低到高的,也就是从左到右是从小到大的。所以这里实际也是从小到大排列的。

这就涉及到了大小端储存了,一个整形在内存中存储是有两种储存方式,大端存储和小端存储。

大端储存:低字节的数据储存在高地址的位置,高字节的数据储存在低地址位置。

小端储存:低字节的数据储存在低地址的位置,高字节的数据储存在高地址位置。

注意这个大小是对于低字节的数据来说的

所以我们可以看到在我使用的VS中它是低字节的数据储存在低地址处,是小端储存。

不同的电脑可能这个储存方式会不同,可能是大端还是小端,至于为什么会有这个大小端储存,这个我也不知道,是跟计算机硬件相关的,我们了解概念以及如何判断就行。

3.2大小端储存的判断

知道了这个大小端的判读,那么我们该如何进行判断呢,有人可能会说直接调试看看就行,但是这是在IDE上,要是一个线上OJ题目呢,你没办法看到内存,这时候该这么去进行判断?

我们可以将问题准换为判断这个机器是大端储存,如果这个问题是真,那么就是大端储存,否则就是小端储存,那么我们要证明就只需要找出一个特殊的例子来证明就行,那1不就是最好的例证吗,1转转为十六进制的数字就是0x00000001,那如果是大端储存就是 00 00 00 01,如果是小端储存就是 01 00 00 00,那我们只需要找到第一个1的第一个字节不就行了,看看这个字节是0还是1就能判断了。

那么问题就变成了怎么找到第一个字节的值,这个也很简单,只需要用到一个char *类型的指针就行,char *类型的指针的访问权限就是访问一个字节,那我们只需要拿到某个变量的地址,将它强转为char *类型就行。

int type_sys()
{
	int n = 1;
    //拿到n的地址,将其强制类型准换为char*
    //解引用找到第一个字节储存的内容,直接返回就行
	return *(char*)&n;
}

int main()
{
	int ret = type_sys();
	
	if (ret)
		printf("小端\n");
	else
		printf("大端\n");
	
	return 0;
}

4 浮点数的存储

4.1一道练习

在开始讲之前,我们先来看一段代码

你们可以先想想结果是什么,这里我就放结果了

怎么样,跟你们想的结果是不是有点出入,没有关系,我们来了解一下浮点数在内存中的存储,随后再来看这个问题,那个时候就能理解了。

4.2浮点数的储存

举个例子:

十进制的7.0,写成二进制是 111.0,相当于 1.11 * 2^2。

那么,按照上面V的格式,可以得出 S = 0, M = 1.11, E = 2

十进制的 -7.0, 写成二进制是 -111.0,相当于 -111.0

那么 S = 1,M = 1.11,E = 2

那么这个跟浮点数在内存中的存储有什么关系呢?

IEEE 754规定:

对于32位浮点数(float),最高一位储存符号位S,接着后8位储存指数E,剩下的23位储存有效数字M。

对于64位浮点数(double),最高一位储存符号位S,接着8位储存指数E,剩下的52位储存有效数字M。

这里就展示一下float的储存。 

4.2.1浮点数的存储过程

除了上面的基础规则外,IEEE 754还对有效数字M和指数E有一些特殊的规定。

在前面的规定中我们可以看到,M是有取值范围的,1 <= M < 2,那么也就是说,对于所有的浮点数的二进制位,一定可以写成 1.xxxxxxx的形式,其中xxxxxxxx是小数部分。

如果前面的数一定是1,那还储存它干什么,不是浪费了一个比特位的空间。

所以规定计算机在储存时,会自动忽略掉前面的1,只储存后面的小数部分。比如1.11,储存的时候就只储存11,在使用的时候将前面的1添加上。

这样的目的就是为了节省一位有效数字,可以提高精确度。

对于指数E,首先要知道的是,E是一个无符号整数,就是E只能是正数

我们刚刚说过在32位下,E占8个比特位,那么就意味着E的取值范围是 0~255;如果是11位,那它的取值范围是0~2047。

但我们都知道,科学计数法中的E是可以出现负数的,那E只能是正数,那该这么解决呢?

这里IEEE 754规定,存入内存时的E的真实值必须再加上一个中间数,对于8位的E,加上的是127,对于11位的E,加上的是1023。比如,2^10,E是10,那么在储存到内存中时就是10 + 127 = 137,就是 10001001。

我们就以7.0为例来演示一下

我们看看内存中是不是这样的

 

我的电脑是小端储存,那么实际上的数是:0x 40 e0 00 00

 

可以看到算出来的数字跟我们推断出的数是一致的。 

4.2.2浮点数取出的过程

对于S和M,在取出的过程中跟存入内存时是反过来的,直接逆着理解就行。但是对于指数E,还有一些特殊规则。

E不全为0或全为1

这时,取出的过程就跟存入的过程是完全相反的过程,直接反着来就行,即将指数E的计算值减127(或1023),得到真实值,再将有效数字M第一位加上1。

就比如这样一个二进制序列

0 01111110 00000000000000000000000

很明显S = 0,M  = 0, 我们计算出来的E = 126,那么真实的E就是126 - 127 = -1,那么这个数还原出来就是 (-1)^0 * 1.0 * 2 * (-1) = 0.5。

E全是0

存储的全是0,那么就是说明,真实值加上127之后还全是0,那么真实值就是是-127,就相当于这个数最后是乘了个 2^(-127),那么这个数肯定是非常非常非常小。

那么这个时候就规定E的真实值是 1-127(或1-1023),就是-126,但是有效数字前面不在加上那个1,直接表示为 0.xxxxxxx的小数。这样做是为了表示 +-0,就是接近于0的很小的数字。

我们看这样一个二进制序列

0 00000000 01000000000000000000000

很明显S = 0,M = 01,E全是0,那么按照我们刚刚所说的,E = 1 - 127,那么这个二进制就表示为 (-1)^0 * 0.01 * 2^(1-127),可以看到,这是一个非常非常非常小的数。

E全是1

我们刚刚说过E的取值范围是0~255,那么如果全是1的话,真实的E就是255 - 127 = 128,那么随后还原出来就相当于乘一个 2^128,那么这是一个非常非常非常打的数字了,所以如果E全是1,就是用来表示 +-无穷的(正负取决于S)。

4.3题目解析

了解完浮点数在内存中是如何储存的,那么我们回到刚刚的题目,先来看第一个环节

为什么*pFloat的值是0.00,*pFloat是一个指向float的指针,我们将n的地址取出来,将它强制类型转换为float*,随后我们以 %f 的形式打印,我们知道 %f 是打印浮点数的,那么你以 %f的形式来打印,程序就会认为*pFloat中的值是一个浮点数的值,那么程序就会把n的二进制序列当成是以浮点数的形式来储存的。

那么我们来看n的二进制序列是什么样的

00000000 00000000 00000000 00001001

如果将它看成是浮点数的储存形式

0 00000000 00000000000000000001001

这样看的话很明显 S = 0,E全是0,M也很小,那么以浮点数的形式来看的话,这个就是一个很小很小的数,所以打印出来的就是0。

了解完第一个,我们来看第二个环节

这回*pFloat我们让它变成了一个浮点数9.0,那么它的二进制序列就是 1001就等价于 1.001 * 2^3,那么就很明显看出 S = 0,M = 1.001,E = 3 + 127。计算E的二进制序列是0111 1111,那么9.0在内存中就是这么储存的

0 10000010 00100000000000000000000

然后我们看到,在第一个printf中,要求用%d的形式来打印n的值,%d是用来打印整形的,你既然是让我用整形的方式来打印,那么我就把你当作是一个整形,那就直接把内存中储存的二进制序列给打印出来了,就是刚刚上面的那一串。

我们掏计算器来算一下

标签:储存,二进制,浮点数,补码,详解,内存,我们
From: https://blog.csdn.net/2301_79843689/article/details/137001444

相关文章

  • 嵌入式中内存分配-栈区、堆区、全局区、常量区和代码区详解
    一、C语言内存分区C语言内存分区示意图如下:1.栈区栈区介绍栈区由编译器自动分配释放,由操作系统自动管理,无须手动管理。栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自......
  • 淘宝扭蛋机源码搭建系统详解
    随着互联网技术的飞速发展,电子商务平台的创新层出不穷。淘宝扭蛋机作为一种新型的互动营销工具,逐渐受到了广大商家和消费者的青睐。本文将详细解析淘宝扭蛋机源码搭建系统,帮助读者了解如何搭建一个高效、稳定的扭蛋机系统。一、扭蛋机源码的选择与获取搭建淘宝扭蛋机系统的......
  • 《C++ Primer 第五版 中文版》第12章 动态内存【阅读笔记 + 个人思考】
    《C++Primer第五版中文版》第12章动态内存【阅读笔记+个人思考】12.1动态内存与智能指针12.1.1shared_ptr类静态内存包括:初始化只读数据段,初始化读写数据段,未初始化数据和常量数据段。详细在下面博客总结:Linux系统下C++程序运行时的内存布局及存储内容,生......
  • 动画图解:九大经典排序算法详解-算法宝App
    重新整理了一遍排序算法,结合自己开发的算法宝App的录屏,转成webp动画一起分享给大家,适合新手。概述时间复杂度(timecomplexity)用来描述算法的运行时间。常用大O符号表述。比如:O(n),O(1),O(logn),O(n2)等。举例:O(n)表示线性级复杂度,表示时间复杂度和元素element数量n成正比。......
  • 【编码器应用】第一节-编码器从从原理到应用详解
    概述:本文内容为常用电机编码器概览,将为您重点介绍编码器大致分类,以及增量编码器与西门子设备的配置连接方式。编码器简介编码器是利用LED光源发出的透射光对码盘进行光电扫描,光电元件接收编码器轴旋转时产生的明暗交替变化,将电机轴的转速和位置转化为电信号反馈给PLC或者驱......
  • 【C语言基础】:数据在内存中的存储
    文章目录一、整数在内存中的存储二、大小端字节序和字节序判断1.为什么有大小端?2.练习三、浮点数在内存中的存储1.浮点数的存储1.1浮点数的存储过程1.2浮点数取的过程四、题目解析     书山有路勤为径,学海无涯苦作舟。创作不易,宝子们!如果这篇文......
  • 在Linux中,什么是虚拟内存?它是如何工作的?
    虚拟内存是一种内存管理技术,它允许操作系统使用硬盘空间来模拟额外的内存资源。虚拟内存的工作原理涉及以下几个关键概念:地址空间:每个进程拥有自己的虚拟地址空间,这个空间对于进程来说是一致的和私有的。虚拟地址空间的大小通常远大于物理内存的大小。分页机制:操作系统将物理......
  • 直播平台制作,优化内存占用不妨试试轻量级数据架构
    直播平台制作,优化内存占用不妨试试轻量级数据架构使用轻量级数据结构在直播平台制作中选择数据结构时,考虑使用轻量级的数据结构,如ArrayList替代Vector,以及StringBuilder替代String拼接。能够在保证功能的前提下,减小内存占用。以下是一些使用轻量级数据结构的方法:1、使用......
  • 直播软件开发,利用对象池实现内存占用优化
    直播软件开发,利用对象池实现内存占用优化对象池是一种重复使用对象的机制,而不是频繁地创建和销毁对象。通过对象池,可以避免创建大量的临时对象,减小对象数量,从而减少直播软件开发中内存占用和垃圾回收的压力。importjava.util.concurrent.ArrayBlockingQueue;importjava.ut......
  • 内存碎片与缓解
    参考资料:https://blog.csdn.net/u014183456/article/details/122031750 内存碎片分为内部碎片和外部碎片外部碎片(ExternalFragmentation):外部碎片是指已分配的内存块之间出现的不连续、无法充分利用的空闲内存空间。外部碎片通常发生在动态内存分配中,当多次分配和释放内......