前言:我们编写程序时,都是用C语言编译器给我们提供好的内置数据类型使用,那么计算机底层如何对这些数据进行区分以及处理的呢?下面进行揭晓。
1.整数在内存中的存储机制
我们知道整数的二进制表示方法有三种,分别是原码、反码和补码。
它又可以细分为有符号整数和无符号整数。有符号的整数,三种表示方式都由两部分组成:符号位和数值位,二进制的最高位被当作是符号位(0表正数,1表负数),剩余位都是数值位。
- 正整数的原码、反码、补码都相同。
- 负整数的三种表示方法都是不一样的。
原码
:将数值按正负数的形式直接写成二进制的得到的就是原码。
反码
·:原码的符号位不变,其余位按位取反即可得到反码。
补码
:对反码
+1得到的就是补码。
对于整型,数据在内存中的存放的其实是它的补码。
原因如下:
在计算机系统中,统一使用补码来表示数值,因为这样可以使加法和减法操作统一,从而简化计算机的硬件设计(CPU只有加法器,另外,补码与原码相互转换,其运算过程相同,不需要额外的硬件电路)。
2.浮点数在内存中的存储
生活中常见的浮点数有:3.1415926、2E10等。C语言中的浮点数类型包括:float
、double
、long
double
类型。
浮点数的表示范围在:头文件float.h
中定义。
首先看一段代码:
#include <stdio.h>
//浮点数在内存中的存储
int main()
{
int i = 10;
float* pf = (float*)&i;
printf("i = %d\n", i);
printf("*pf = %f\n", *pf);
*pf = 10.0;
printf("i = %d\n", i);
printf("*pf = %f\n", *pf);
return 0;
}
看上述代码,同一个数以不同类型解析会是什么样的结果呢?难道是:
i = 10 ?
*pf = 10.0?
i = 10?
*pf = 10.0?
如果你是这么想的话,那么恭喜你只对了一半。请看运行结果:
通过观察运行结果可以得出一个结论 - 整数和浮点数在内存中的存储是不一样的。
2.2 浮点数在内存的存储
分析:浮点数在计算机内部是如何表示的。
国际标志IEEE(电气和电子工程协会)754规定,任意一个二进制浮点数V可以表示成下面的形式:
比如:十进制的10.5,写成二进制是1010.1,科学计数法表示:1.0101 * 2^3
使用V的格式来说明的话:S = 0,M = 1.0101,E = 3.
IEEE 754规定了:
对于32位的浮点数,最高的一位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M。
对于64位的浮点数,最高的一个为存储符号位S,接着的11位存储指针E,剩下的52位存储有效数字M。
分析:
#include <stdio.h>
int main()
{
//10.5
float f = 10.5f;
//10.5 -- 十进制
//1010.1 -- 二进制
//(-1)^0 * 1.0101 * 2^3
//S = 0
//M = 1.0101
//E = 3
//3 + 127 = 130
//0100 0001 0010 1000 0000 0000 0000 0000 - 浮点数(float)在内存中的存储
//41 28 00 00 -- 16进制
return 0;
}
调试结果:
需要注意的是:举例不要举特殊值,比如:3.1415926……
2.3 浮点数存储过程
IEEE 754 对有效数M和指数E有一些特殊规定。
我们知道M的范围是1≤M<2
,也就是M可以写成1.xxxxx
的形式,其中xxxx
表示小数部分。IEEE 754规定,计算机内部存储M时,默认这个数的第一位是1,因此把第一位数给省略,只保留后面的小数部分。如1.0101,只保留0101,等到读取时再把第一位的1加上。这种做法的目的是节省一位有效位。拿32位浮点数举例,M只有23位有效数字,将第一位的1舍去之后,便可以保留24有效数字。
指针E的情况略复杂些
E是一个无符号整数(unsigned int)
表示如果E为8位时,它的取值范围是0~255;如果E为11位时,它的取值范围是0 ~ 2047.IEEE 745规定,存入内存时E的真实值必须要加上一个偏移量,对于8位的E,它的偏移量是127;对于11位的E,它的偏移量是1023。
例如:
2^10的E是10,所以保存32位浮点数时,必须保存成10+127 = 137,二进制为10001001.
2.4 浮点数的读取过程
指数E从内存中取出可分为三种情况:
情况一:E不全为0或者不全为1
浮点数的计算规则:指数E的计算值 - 127(或1023),得到实际值(真实值),再将第一位的1加到M前。
举例:
0.75的二进制形式为0100 1011,由于规定正数部分必须为1,即将小数点右移两位,得到1.001011*2^(-2),其阶码为-2+127 = 125,表示为01111101,而尾数1.0去掉整数部分为0,补齐0到23位,则可表示为:
0 01111101 000000000000000000
情况二:E为全0
此时,浮点的指数E等于1~127(或1 ~ 1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxx的小数。目的是为了表示±0,以及接近0的很小的数字。
0 00000000 00100000000000000000000
情况三:E为全1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位S)。
0 11111111 00100000000000000000000
2.5 代码解析
回看这段代码:
#include <stdio.h>
int main()
{
int i = 10;
float* pf = (float*)&i;
printf("i = %d\n", i);
printf("*pf = %f\n", *pf);
*pf = 10.0;
printf("i = %d\n", i);
printf("*pf = %f\n", *pf);
return 0;
}
分析: