欢迎大家继续来和我一起学习,今天我们要学习的是数据在内存中的存储。
目录
1.整型在内存中的存储
我们知道,整型的二进制表示方法一共有三种,分别是:原码,反码和补码。
整数分为有符号整数和无符号整数,对于有符号的整数来说,它的二进制表示由符号位和数值位组成,正数的符号位为0,负数的符号位为1。而对于无符号的整数,所有的二进制位都是数值位。
而在内存中,整型其实是以补码的形式进行存储的,之所以这样也是有原因的:
2.⼤⼩端字节序和字节序判断
我们知道,内存中的每个地址指向一块空间,这些空间由比特位组成,对于整型数据,他们在内存中的存储方式又是怎样的呢?
对于在内存中一个字节大小的空间,它的每个比特位的位置是不同的,对于整数20,它的二进制表示在一个字节内为00010100,而在内存中就有大端和小端两种储存方式。下面是两种储存方式下的图解:
大端存储:
小端存储:
2.1 大端与小端的判断
可以看到在作者的电脑上打印结果为1,说明作者的电脑是小端排序的,
我们来看一道关于小端排序的题:
#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;
}
对于数组a来说,数组中每个元素用十六进制可以写成下图形式,由于是小端排序,所以写成10 00 00 00,但要注意的是10才是1的十六进制在小端排序的正确形式。
我们接下来看这道题,首先来看ptr1,数组的地址+1跳过一个数组的长度,再强制转化为jin* 类型
而ptr1[ -1 ]表示的是ptr1-1,由于ptr1被强制转换为了int*类型,所以-1时跳过4个字节,此时ptr1就指向4。
再来看ptr2 ,先将ptr2强制转换为int类后+1,我们知道ptr2 代表首元素地址,而整型+1就跳过1,所以ptr2指向的位置应跳过一个字节。再转换为int*类型,对其解引用,应该访问四个字节,且最后打印结果以十六进制输出,所以结果应该为2000000 。
3. 浮点数在内存中的存储
讲完了整数在内存中的存储,接下来我们来学习一下浮点数在内存中的存储。
对于一个浮点数,它有两种写法:小数型和指数型(3.14159、1E10等)而对于每一个以小数形式写出的浮点数,也可以改写为指数型写法。
而在内存中,浮点数的储存是以指数形式进行储存的。
要把一个小数写成指数形式,我们还要知道是怎样变换来的,对于一个十进制数123,我们可以写成1.23*10^2,这一方法对二进制数同样适用。
- V = (−1) ^ S * M * 2^E
- (−1)^S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
- M 表⽰有效数字,M是⼤于等于1,⼩于2的
- 2 ^ E 表⽰指数位
比如说十进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2 ,根据上⾯V的格式,可以得出S=0,M=1.01,E=2。
对于浮点数的内存空间分配,同样有规定:
32位浮点数,也就是float类型的数,一个float类型的数据分配四个字节的空间,在这四个字节当中,有1位用来储存S,8位用来存储E,剩下23位全用来储存有效数字M,同理可得64位浮点数,也就是double类型的数据的空间分配。这也就是为什么double类型的数据精度要比float类型数据高的原因。
两图分别是float和double类型的空间分配
因为二进制数写成指数部分M的整数部分必为1,所以在储存数据的时候在M的空间中只存储小数部分,这样也能有效提高精度。
而对于指数E,有更复杂的情况,在内存中储存的E必须是无符号整数,但事实上指数部分可能会存在负数,所以IEEE 754规定,存⼊内存时E的真实值必须再加上⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。⽐如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
这样就能保证存储的E全为正数。
3.1 浮点数在内存中的取出
对于指数E从内存中取出还可以再分成三种情况:
1.E不全为0或不全为1
这时,浮点数就采⽤下⾯的规则表⽰,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1。
2.E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字。