什么是表达式求值?数据在内存中是怎样存放的?

本文详细介绍了整数(原码、反码、补码)在内存中的存储方式,整型提升和算术转换的概念,以及大小端字节序的区别。此外,还探讨了浮点数的存储规则,如IEEE754标准和比较大小的问题。
摘要由CSDN通过智能技术生成

1.整数在内存中的存储

  在介绍数据在内存中的存储之前,我想先给大家介绍一下原码、反码以及补码的概念,有了这个概念我们就可以更加深刻的理解数据在内存中存放的方法。

  什么是原码?我们都知道,数据在计算机是以二进制的形式(0或1的形式)存储的,每一个二进制位的权重是2^(n-1),其中的n表示位数,即:第一个二进制位的权重是2^0;第二个二进制位的权重是2^1;第三个二进制位的权重是2^2....以此类推,例如现在有一个二进制数字110,转换成十进制数字就是:1*2^2+1*2^1+0*2^0。那么直接将十进制数字转换成二进制数字的形式就是原码,值得一提的是:有符号的整数的二进制表示方法中,最高位是符号位,其余是数值位,其中0为正,1为负,无符号的整数全为数值位。
  那么什么是反码呢?就是将原码的符号位不变,其他位按位取反就可以得到反码。举个例子,-10的原码是:10000000000000000000000000001010,转换成反码就是:11111111111111111111111111110101。

  什么是补码呢?将反码+1就可以得到补码。-10的补码就可以写成:11111111111111111111111111110110。

  对于整型来说,数据在内存中存放的是补码。为什么要这么做呢?实际上,计算机的CPU中只有加法器,使用补码进行运算可以将加法和减法统一处理,同时补码与原码之间相互转换的过程是相同的,即:都是取反+1,不需要额外增加硬件电路。(注:正数的原码、反码、补码是相同的),当我们向内存中获取数据的时候,计算机会将补码转换成原码并将数据输出。

2.表达式求值

2.1  整型提升

  什么是整型提升?在探讨这个问题之前我们先来看一段代码:

  

我们看到,当我们以char类型计算125+10这个数据时,理应输出的结果是135,但实际上输出的结果是-121,这是为什么呢?实际上,这里就涉及C语言中的整型提升。

  C语言中的整型算数总是以缺省整型的精度来进行的。为了获取这个精度,表达式中的字符(字符是整型家族的,字符是以ASCII码形式储存)和短整型操作数(char,short)在使用前被转化成普通整型,这种转换被称为整型提升。怎么理解这句话呢?整数是int类型的数据,如果前面的操作数是短整型,那么就会发生截断,但在进行加法运算时又会将数据提升至int类型;完成加法运算后又会发生截断,在输出数据时又会发生整型提升。那么整型提升的方法是什么呢?即就是:有符号的整数提升是按照变量的数据类型的符号位来提升的;无符号整数高位补零

  我们用上述方法分析 上述代码,分析结果如下图所示:

  

  char数据类型分为char,signed char以及unsigned char,注:C语言规定char类型默认是否带有正负号是由当前系统决定的,也就是说char不等同于signed char,也有可能是unsigned char,这一点与int不同,int就等同与signed int,但在大多数编译器中char就等于signed char。

  从上述的分析中我们直到在进行加法运算时会发生整型提升,但是为什么会进行整型提升呢?事实上,表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的数据相加,在CPU执行时也要先转换为整型操作数的标准长度。

 2.2  算术转换

  整型提升是针对小于整型类型的数据,那么大于等于整型类型的数据又该如何呢?这里我们就要介绍一下算术转换。

  如果我们要计算的两个操作数属于不同类型,如果不将两个操作数转换成相同类型,那么就无法完成计算,将两个不同类型的大于等于int类型的操作数转换成相同类型的方法就叫算数转换。转换方法如下图:

为防止看不清我把从上到下的类型依次打出来:long  double、double、float、unsigned  long  int、long  int、unsigned  int、int 。那么是怎样完成转换的呢?举个例子,假如现在要将int类型的数据和float类型的数据相加,那么我们要把int类型的数据转换成float类型的数据,然后再进行计算。

  以上就是表达式求值的方法。

3.大小端字节序及其判断

  我们在了解了数据在内存中的存储后,我们来观察一个细节:

  我们看到,在vs2022环境中,数据在内存中是倒着存储的,这是为什么呢?下面我们将介绍大小端字节序。

  什么是大小端?其实超过一个字节的数据在内存中的存储就存在存储顺序问题,按照不同的存储顺序,我们分为大端存储和小端存储。

大端存储:数据的低字节内容保存在内存的高地址处;高字节内容保存在内存的低地址处。

小端存储:数据的低字节内容保存在内存的低地址处;高字节内容保存在内存的高地址处。

我们画图理解一下:

总结一下:大端低位高址;小端低位低址。

  为什么会有大小端呢?这是因为,在计算机系统中我们是以字节为单位的,每个地址都对应着一个字节,一个字节为8bit位,但在C语言中除了8bit的char之外,还有16bit的short,32bit的long,另外,对于位数大于8位的处理器,例如32位和64位,由于寄存器宽度大于一个字节,那么必然存在着如何将多个字节安排的问题。因此就导致了大端和小端存储模式。

4.浮点数在内存中的存储

  浮点数的家族包括:float、double、long  double类型,浮点数的表示范围在float.h中定义。在我们讨论浮点数在内存中的存储问题之前,我想先请大家看一个代码:

int main()
{
	int n = 9;
	float* pfloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pfloat的值为:%d\n", *pfloat);

	*pfloat = 9.0;
	printf("num的值为:%d\n", n);
	printf("pfloat的值为:%d\n", *pfloat);
	return 0;
}

 你认为上述代码的结果是什么呢?我们在vs2022中运行,结果如下:

  此时我们不禁疑惑,为什么将9强制类型转换成float类型的数据之后的值为0呢?为什么9.0的结果是一个很大的数呢?

  下面我将对你们的疑惑一一解答。首先我们已经知道,数据在内存中是以二进制的形式储存的,那么是不是也意味着浮点数类型的数据也是以二进制的形式存储的呢?答案是肯定的。根据国际标准IEEE(电气和电子工程协会)754标准,任意一个二进制浮点数V可以表示成下面的形式:

                                                         V=(-1)^S*M*2^E

         (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数

         M表示有效数字,M是大于等于1,小于2的

         2^E表示指数位

举个例子,5.5写成二进制是101.1,依照上面的格式可写成1.011*2^2,可以类似于十进制中的科学计数法,其中S=0;M=1.011;E=2。正是由于这样的规定,我们在内存中存储浮点数时只需要将S、M、E的数据存入内存,IEEE754规定:对于32位的浮点数,最高位储存符号位,接着8位储存E,剩下的23位存储M;对于64位浮点数,最高位为符号位,接着11位储存指数E,剩下的52位储存M。

浮点数的存储过程;

  对于有效数字M和指数E,由于M是1<=M<2,也就是说无论M的值是什么,它总是1.xxxxxx,因此IEEE754规定,计算机在保存M的时侯,第一位的1可以省略,只保存后面的部分。

  对于指数E,首先,E是一个无符号的整数,但是如果浮点数在转换成二进制的形式后M的值小于1,那么为了保证M的值大于等于1,小于2,指数E就应该小于0,例如:0.5转换成二进制的数字应该为:1*2^(-1)。因此为了防止这种情况的出现,IEEE754中规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数时127;对于11位的E,这个中间数是1023。因此对于0.5而言,E的值应该是(-1)+127=126,转换成二进制为:01111110。

  其次,指数E从内存中取出还可以再分成三种情况:

(1.)E不全为0或不全为1,这时,浮点数就采⽤下⾯的规则表⽰,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1。

(2.)E全为0,这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,而是还原为0.xxxxxx的小数,由此就表示接近于0的很小的数字。

(3.)E全为1,这时,如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s)。

  现在我们回过头分析最开始的那段代码,⾸先,将 9 的⼆进制序列按照浮点数的形式拆分,得到第⼀位符号位s=0,后⾯8位的指数E=00000000 ,最后23位的有效数字M=000 0000 0000 0000 0000 1001。由于指数E全为0,所以符合E为全0的情况。因此,浮点数V就写成:
V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)。显然,V是⼀个很⼩的接近于0的正数,所以⽤⼗进制⼩数表⽰就是0.000000。
  再看第2环节,浮点数9.0,为什么整数打印是 1091567616?⾸先,浮点数9.0 等于⼆进制的1001.0,即换算成科学计数法是:1.001×2^3。所以:9.0  =  (−1)   ^0* (1.001)  ∗  23
那么,第⼀位的符号位S=0,有效数字M等于001后⾯再加20个0,凑满23位,指数E等于3+127=130,即10000010所以,写成⼆进制形式,应该是S+E+M,即
1 0 10000010 001 0000 0000 0000 0000 0000
这个32位的⼆进制数,被当做整数来解析的时候,就是整数在内存中的补码,原码正是
1091567616 。

知道了浮点数在内存中存储的规则后,我们可以总结出以下几点:

1.浮点数在内存中可能无法精确保存。

2.double类型的精度比float高。

3.两个浮点数在比较大小时,直接用==可能存在问题。

以上就是本篇博客的全部内容,由于编者水平有限,如果在阅读的时候发现问题欢迎指正。

最后如果觉得本文对你有所帮助拜托大家多多点赞关注加收藏,你的点赞就是小编努力更新下去的动力 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编码的傅同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值