【C语言进阶】数据的存储详解

发布于:2023-01-23 ⋅ 阅读:(323) ⋅ 点赞:(0)

目录

一.整形在内存中的存储

1.原码、反码、补码

2.大小端的介绍 

二.浮点型在内存中的存储 

1.引例

2.浮点数存储规则 


一.整形在内存中的存储

我们知道,一个变量的创建需要在内存的开辟一块空间,开辟空间的大小是由变量本身的类型决定的,那么整形变量在内存中是如何存储的呢?

比如下面这段代码:

#include<stdio.h>
int main()
{
	int a = 10;
	int b = -10;
	return 0;
}

我们知道,int类型的变量的创建需要开辟4个字节的空间,为了探究它们在内存中的存储方式,我们需要先了解以下这个概念。

1.原码、反码、补码

我们知道在计算机中,所有的数据都是通过二进制将0和1存储在内存中。在计算机中,整数有三种2进制表示方法,即原码、反码和补码。三种表示方式都是由符号位数值位表示。符号位由0表示正1表示负,而数值位中,正整数的原码、反码、补码都相同负整数中原码、反码、补码都不相同

原码 直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码 将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码 反码+1就得到补码。

对于整形数据,在计算机中是以补码的形式存储的。这是因为使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

我们回到刚开始的代码,在编译器中看看整数的存储方式:

 我们发现,对于a和b分别存储的是补码。但是我们发现顺序有点不对劲。按理来说,a的存储方式不应该是00 00 00 0a吗?为什么却是0a 00 00 00呢?我们接着往下看。

2.大小端的介绍 

大端 即大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
小端 即小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。

那么为什么要由大小端呢?

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

二.浮点型在内存中的存储 

1.引例

在上面我们了解到了整数在内存中的存储形式,那么浮点型的存储是否和整数一样呢?我们可以通过下面这串代码来观测一下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	*pFloat = 9.0;
	printf("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}

如果不了解浮点型在内存中的存储形式,大部分人可能会认为这串代码运行的结果应该是:

n的值为:9
*pFloat的值为:9.000000
num的值为:9
*pFloat的值为:9.000000

那么究竟是不是呢?我们运行看看:

 可以看到,对于上述代码,运行的结果和我们的预期差距还是有点大的。为什么会是这样的结果呢?我们接着往下看。

2.浮点数存储规则 

根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:

(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,M>=1&&M<2。
2^E表示指数位。

举个列子:

十进制中的5.5,二进制表示为101.1,相当于1.011*2^2。那么对照上面的格式

S=0

M=1.011

E=2

十进制中的-5.5,二进制表示为-101.1,相当于-1.011*2^2。对照格式

S=1

M=1.011

E=2

IEEE 754规定

对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。 

IEEE 754对有效数字M和指数E,还有一些特别规定

因为M>=1&&M<2,即M可以写成1.xxx...的形式(xxx表示小数部分) 

因为M前面总是为1,因此,IEEE规定,在内存中保存M时,舍去前面的1只保留后面的小数部位。

比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

而对于E的保存,情况比较复杂:

E在内存中是以一个无符号整形(unsigned int)保存的,也就是说,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是我们知道,在科学计数法中,E是可以为负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127对于11位的E,这个中间数是1023

比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
然而,指数E从内存中取出还存在三种情况:

1.E不全为0或不全为1

这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。

比如:
0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,表示为1.0*2^(-1),其中E为-1+127=126,表示为01111110,而M=1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:

0 01111110 00000000000000000000000。

2.E全为0

规定,浮点数的指数E等于1-127(或者1-1023)。

这是因为如果E+127(或者+1023)后还全为0,则真实的E=-127,当E=-127时,那这将是一个非常小的一个数(非常接近0了),所以IEEE 754就直接规定了E的真实值。

并且,有效数字M不再加上第一位的1,而是还原为0.xxx...的小数。这样做是为了表示±0,以及接近于0的数字。
3.E全为1

这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位S)。

这是因为E全为1时,(2)11111111=(10)255,1.xxx...*2^255会是一个非常大的数字,因此就规定如果有效数字M全为0,表示±无穷大。

了解了这些,我们就能解释开始那串代码的输出结果为什么会是那样了,下面就来解释下原因。

( 不考虑大小端)

 9的二进制表示为:00000000 00000000 00000000 00001001(9在内存的的补码)

因此第一个printf以整形打印所以输出9。

第二个printf是以float类型打印*pFloat,因为这是个浮点型指针,以浮点数的方式读取9,所以最高位被解读成了S(符号位),接下来8位解读成了E(指数位),剩下的被解读成了M(有效数字),因此被解读成了(-1)^0*0.00000000000000000001001*2^-126,所以这个数非常接近0了,所以打印出了0.000000。

第三个printf,打印之前,先是将9.0以浮点数的形式放到*pFloat中,而(10)9.0=(2)1001.0=1.001*2^3=(-1)^0*1.001*2^3。所以存进内存中就是

01000001000100000000000000000000。打印出来时是以整形打印,所以=1091567616。

最后一个printf由于存进去的是浮点型,而且以浮点型打印,所以最后结果就是浮点型,即9.000000。

至此,所有内容都已经讲完,感谢你的观看,如果觉得写的不错,请点赞收藏转发支持一下!


网站公告

今日签到

点亮在社区的每一天
去签到