一、整数在内存中的存储
首先,整数的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 00
return 0;
}
从上述代码中可以感受到大端和小端的具体不同之处,那么我们怎么判断所用机器是大端还是小端呢?
#include <stdio.h>
int check_sys()
{
int n = 1; //十六进制表示为0x00 00 00 01
return *(char*)&n;
//将n的地址强制转化位char*类型,然后解引用,即可取出最低一位地址对应的数字,然后判断它的值即可
//小端:01 00 00 00
//大端:00 00 00 01
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\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)^S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
M 表⽰有效数字,M是⼤于等于1,⼩于2的
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
- 对于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 00
int* ptr1 = (int*)(&a + 1);
//指针+1取决于指针的类型
int* ptr2 = (int*)((int)a + 1);
//整数+1就是+1,向后走一个字节,跳过01,访问的是00 00 00 02
//因为是小端模式,所以低地址存的是低字节数据,访问的是20 00 00 00
printf("%x,%x", ptr1[-1], *ptr2); //4,2000000
return 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,看作0
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n); //9
printf("*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 0000000
printf("n的值为:%d\n", n); //1091567616
printf("*pFloat的值为:%f\n", *pFloat); //9.000000
return 0;
}