整数在内存中的存储
我们都知道,数据在内存中是以二进制的方式进行存储的,整数的二进制有原码,反码和补码三种,而真正在内存中存放的就是补码。
我们先简单了解一下这三种表示方式:
正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同。
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
为了方便,我们简单用8个比特位来解释一下,我们主要说一下负数
-6 的二进制表示:
原码:10000110
反码:11111001
补码:11111010
前面的1表示符号位,正数的话就是0
所以对于无符号整数来说,没有符号位,所有比特位全部表示数值
对于以下代码,可以思考以下运行结果
int main() {
unsigned int i;
for (i = 9; i >= 0; i--) {
printf("%u ", i);
}
return 0;
}
结果是程序会陷入死循环,因为是无符号的整型,当 i 减到 0 之后会变成一个特别大的数字,一直都不会小于0,就是下图中的效果
大小端和字节序的判断
什么是大小端呢?
我们通过一个例子来说明
可以看出,为什么在内存中n的存储是倒着的
这就是我们要说的大小端的问题了
- 大端字节序:在这种模式下,数据的高字节保存在低地址,低字节保存在高地址。也就是说,最高有效字节排在最前面,最低有效字节排在最后面。例如,十六进制数0x12345678,在内存中的存储顺序是:12 34 56 78。
- 小端字节序:与小端相反,数据的低字节保存在低地址,高字节保存在高地址。即最低有效字节排在最前面,最高有效字节排在最后面。以十六进制数0x12345678为例,其在内存中的存储顺序是:78 56 34 12。
大端的优势在于第一个字节就是高位,容易判断数据的正负性;而小端的优势在于最后一个字节是高位,可以依次取出相应的字节进行运算,并且最终会把符号位刷新,运算起来更高效。
我们也可以写一个程序来判断当前机器是大端存储还是小端存储:
#include <stdio.h>
int check_sys()
{
int i = 1;
return (*(char *)&i);
}
int main()
{
int ret = check_sys();
if(ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
浮点数在内存中的存储
存储过程
例如5.5的二进制表示为101.1,可以转化为(-1)^0 * 1.011*2^2,此时S=0,M = 1.011,E=2
float 类型的小数点后面可以存23个比特位,double类型 小数点后面可以村 52 个比特位,
M的存储
在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的小数部分。例如,如果要存储1.01这样的数,实际上只需要保存01,而在读取时,再将第一位的1加上去。这样做的目的是为了节省1位有效数字。在32位浮点数中,将尾数的第一位默认为1后,剩下的位数就可以用来表示更多的有效数字。
指数E的存储
- 在存储过程中,指数需要经过一个偏移处理。这是因为在计算机中,指数可以是正数或负数,而直接存储负数在计算机中是不方便的。因此,IEEE 754标准定义了一个偏移量(对于单精度浮点数是127,对于双精度浮点数是1023)。实际存储的指数值是原指数值加上这个偏移量。
- 指数部分在内存中占据一定的位数,对于单精度浮点数通常是8位,双精度浮点数则是11位。这些位数用于精确地表示指数的范围。
我们来用一个例子解释以下:
对于5.5来说,二进制为101.1,也就是1.011*2^2,又可以转化为 (-1)^0*1.011*2^2
此时,S=0,M=1.011,E=2
但是我们在内存中存要加上偏移量,也就是2+127 = 129
内存中就是 0 10000001 01100000000000000000000
换成16进制就是 40 B0 00 00
接下来我们验证一下
明显,f的内存和我们计算的一样,只不过由于小端存储是 00 00 b0 40
取出过程
取出过程正好是和存储相反的,也就是指数E的值减去127(1023),得到真实值,再将有效数字前面加上第一位的1,因为之前的1是没有存入的。
另外还有两种特殊情况:
E全为0,说明E是-127这样的数字,也就是1的-127次方,一个特别接近0的数字,表示+-0以及接近0的很小的数字
E全为1,同理,此时表示的是一个非常大的数字。
那么数据在内存中的基本存储原理就分享到这里了,有机会再补充。