深入解析内存中的整数与浮点数存储
一、整数在内存中的存储
首先,整数的2进制表示方法有三种,分别是原码、反码和补码。整数在内存中存储的是二进制的补码。补码又分为符号位和数值位两部分,最高位的一位是符号位,用‘0’表示正数,用‘1’表示负数;其余则是数值位。不过,我们在调试窗口中观察内存时,为了方便展示,显示的是16进制的值,如下图所示。
二、大小端
从上图中可以看到,为什么我们给n赋值位0x11223344,在内存中观察的时候是44 33 22 11呢?这就要引出新的概念”大小端“了。
所谓大端(存储)模式,是指数据的低位字节内容保存在内存的高地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。而小端(存储)模式是指数据的低位字节内容保存在内存的==低==地址处,⽽数据的==⾼==位字节内容,保存在内存的⾼地址处。
下面我们用几段代码来切实的感受下大小端的不同。
#include <stdio.h>
int main()
{int n=1; //转为16进制位:0x00 00 00 01 在左边的是高位,右边的是低位// 内存 低位 ---> 高位// 在大端模式下存储的是:00 00 00 01// 在小端模式下存储的是:01 00 00 00return 0;
}
从上述代码中可以感受到大端和小端的具体不同之处,那么我们怎么判断所用机器是大端还是小端呢?
#include <stdio.h>int check_sys()
{int n = 1; //十六进制表示为0x00 00 00 01return *(char*)&n; //将n的地址强制转化位char*类型,然后解引用,即可取出最低一位地址对应的数字,然后判断它的值即可//小端:01 00 00 00 //大端:00 00 00 01
}int main()
{int ret = check_sys(); if (ret == 1)printf("小端\n"); elseprintf("大端\n"); return 0;
}
下面我们做个练习来巩固下学习成果吧。(解析在文章末尾)
#include <stdio.h>
//X86环境⼩端模式下,输出结果是什么?int main()
{int a[4] = { 1, 2, 3, 4 }; int* ptr1 = (int*)(&a + 1); int* ptr2 = (int*)((int)a + 1); printf("%x,%x", ptr1[-1], *ptr2); return 0;
}
三、浮点数在内存中的存储
浮点数在内存中存储时首先还是将十进制转换为二进制,使用乘2取整法就可以啦(将小数部分不断乘以2,记录整数部分(0或1),直到小鼠部分为0或达到所需精度,最后将整数部分顺序排列)。
浮点数转换为二进制时,小数点后数字的权重分别是2^(-1),2^(-2),2^(-3),……
按照国际标准IEEE 754,我们需要将二进制浮点数V表示为以下形式:
V=(-1)^S * M * 2^E
1. (−1)^S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
2. M 表⽰有效数字,M是⼤于等于1,⼩于2的
3. 2^E 表⽰指数位,E为⼀个⽆符号整数
举两个例子,⼗进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2,可以得出S=0,M=1.01,E=2;⼗进制的-5.0,写成⼆进制是:-101.0 ,相当于-1.01×2^2 。那么,S=1,M=1.01,E=2。
在内存中时按照 S E M 的顺序依次存储的,有以下要求:
1.对于32位的浮点数(float),最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
2. 对于64位的浮点数(double),最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M
存储有效数字 M 和指数 E 时,还有一些特别的规定
1.在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的小数部分。⽐如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去
2.存⼊内存时E的真实值必须再加上⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023
四、浮点数取的过程
浮点数在取时可分为3种情况。
E 不全为0或1时
指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1
E 全为0时
浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字
E 全为1时
如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s)
做个题目巩固下浮点数的学习成果吧。(解析在最后)
#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("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); return 0;
}
五、题目解析
第一题
#include <stdio.h>
//X86环境⼩端字节序int main()
{int a[4] = { 1, 2, 3, 4 }; //在小端模式下的存储如下//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00int* ptr1 = (int*)(&a + 1); //指针+1取决于指针的类型int* ptr2 = (int*)((int)a + 1); //整数+1就是+1,向后走一个字节,跳过01,访问的是00 00 00 02//因为是小端模式,所以低地址存的是低字节数据,访问的是20 00 00 00printf("%x,%x", ptr1[-1], *ptr2); //4,2000000return 0;
}
第二题
#include <stdio.h>int main()
{int n = 9;//二进制:1001->补码:00000000 00000000 00000000 00001001//用浮点数视角看的话就是:0 00000000 00000000 00000000 0001001//E=1-127=-126//E全为0,有效数字M,取出后不再加上第一位的1//(-1)^0 * 0.00000000 00000000 0001001 * 2^(-126) -> 无限接近于0,看作0float* pFloat = (float*)&n;printf("n的值为:%d\n", n); //9printf("*pFloat的值为:%f\n", *pFloat); //0.000000*pFloat = 9.0; //以浮点数的视角存储9.0//(-1)^0 * 1.001 * 2^3//S=0,M=1.001,E=3// 3+127=130 //0 130 3(001) //0 10000010 00100000 00000000 0000000printf("n的值为:%d\n", n); //1091567616printf("*pFloat的值为:%f\n", *pFloat); //9.000000return 0;
}