你真的了解C吗--深度剖析数据在内存中的存储(C进阶)

整形在内存中的存储

当我们创建一个变量是要在内存中开辟空间,而空间的大小是根据不同的类型而决定的!!!

那数据在所开辟内存中到底是如何存储的呢?
如果是整形,则以补码形式存储

原反补

计算机中有符号数有三种表示方法,即原码、反码和补码。而这三种表示方法均有符号位数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。正数的原码、反码和补码都是相同的。

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

单纯的计算机

在计算机系统中,数值一律用补码来表示和存储。为什么呢?原因是使用补码。可以将符号位和数值位统一处理;与此同时,假发和减法也可以统一处理(CPU只有加法器)。补码与原码相互转换,起运算过程是相同的,可以大大减小对硬件电路的需求。
那么问题来了!既然CPU只有加法,那按这个道理补码往原码转不是还要再减1吗?是的,可以这么认为。但是研究计算机的大佬们还发现把补码看成原码,再次进行原反补竟然还能转回去!!!所以计算机就是这么做的。
在这里插入图片描述

实例讲解

实例1:数据以补码形式存储!

在这里插入图片描述
这里大家不要疑惑,为什么是十六进制?十六进制是给我们看的,这是可以在编译器理调的,而二进制是才是给计算机看的。

实例2:原反补约束谁?变量?数据?

有很多人可能认为,我们编写代码定义的变量类型是什么,那存的数据就是怎么样的,这是错误的理解!
在这里插入图片描述
这个例子就很明显的可以看到,我们定义的是无符号char,但是计算机还是将原始数据按照有符号原反补转换之后存储进内存。

实例3:类型的意义

请思考如下代码的结果是什么?
在这里插入图片描述
在读取一个变量的时候,是该变量的类型,决定了如何看待该变量内部的二进制序列的含义,不论如何看待二进制序列,二进制序列本身是不发生变化的,但是经过类型解释二进制代表的含义是会发生对应的变化的,这也就是类型的意义。
那么现在大家对上面这道题的答案了解了吗?a = -1,b = -1,c = 255,a和b我们自然不用说了,那c为什么是255呢?我们说存储的时候计算机存储的是原始数据的补码,但是当我们读取变量的时候看的是变量的类型,这时候变量类型会解释二进制代表的含义,c是无符号的,所以什么符号位,什么数值位计算机都不管了,全部看成数值位。当下二进制序列位1111 1111,即255。

数据的取值范围

读到这里大家可能说取值范围我知道啊,书上表格里写的清清楚楚明明白白的,没错,是这样,但是你真的清楚吗?unsigned char 的取值范围是[0,255] ,这个简单啊,char占1字节,8bite,也就是从0000 0000 ~ 1111 1111,那么signed char的取值范围是[-128,127],这是为什么?有符号首位肯定是符号位,其他位数值位,但是大家有没有注意到有两个0,一个是0000 0000,另一个是1000 0000,也就是±0,对于1000 0000大佬们将它规定为-128,这里我们来解释一下(涉及截断问题)
在这里插入图片描述
由此我们也可以推而广之:unsigned int的取值范围是[0,2^ 32-1],而signed int的取值范围是[-2^31,2 ^31-1]。

大小端存储

内存访问的基本单位是字节,只要是字节,每个字节都有地址,只要有地址,地址值就会有大小之分。数据按照字节为单位,可以被划分成若干部分,每一部分对应的“权值”是不同的,只要不同也就有高低之分。把数据的低权值字节数据,放在低地址字节里面,这种存放方式就叫做小端存储。注意:写入和读取的时候要考虑大小端问题。我们的计算机大多都是按小端存储来设计的。
在这里插入图片描述

浮点型内存存储

问题思考

想一想这个代码的结果是什么?

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;
}

浮点数的表示

浮点数可以表示为科学计数法的,例如123.123可以表视为1.23123e2,根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数可以表示成下面的形式:

  1. (-1) ^ S * M * 2 ^E
  2. (-1) ^ S 表示符号位,当S = 0 , 浮点数为正数,当S = 1 ,浮点数位负数
  3. M表示有效数字,大于等于1 ,小于2
  4. 2 ^ E表示指数位

举例来说:十进制的5.0携程二进制是101.0,相当于1.01*2^2。那么,按照上面的格式,可以得出S = 0,M = 1.01,E = 2。对于32位的浮点数,最高位是符号位S,接着的8位是指数E,剩下的23位为有效数字M对于64位的浮点数,最高位是符号位S,接着11位是指数E,剩下的52位为有效数字M。

M的知识点

前面说过,M的取值范围是[1,2],也就是说,M可以写成1.xxxx的形式。在计算机内部保存M时,默认这个数的第一位总是1,因为可以被舍去,只保存后面xxxxx的部分,所以23位的有效数字将第一位社区以后,等于可以保存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全为0

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为 0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

E全为1

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

答案揭秘

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

在这里插入图片描述

空类型

  1. void * 能定义变量,甚至能赋值,说明void*本身给变量开辟了空间
  2. 但是,void*对应的变量不能被直接解引用!!!
  3. void*可以用来接收任意类型,常用来接收任意指针
  4. void不能用来定义变量,但是能用来作为函数返回值,表示无类型返回

总结

对于编程学习,我个人认为把很多知识点都吃透,也许有很多我们觉得没有必要学习的东西,但是我发现把这些东西都把控好就会将计算机的体系联系起来。就比如我们讲了原反补,这个知识点在很多计算机相关专业中都有提到,但是并没有讲到C语言中到底是怎么样的!这篇博客我们主要讲了整形和浮点,后面还会深挖指针、构造类型。创作不易,读者如果觉得博主写的还不错,请关注我,为我点赞,谢谢!

猜你喜欢

转载自blog.csdn.net/zSoaring/article/details/107202559