相信初始编程的你心中一定有这样的疑惑,在内存中,整数是怎么存储的?小数又是怎么存储的?负数与正数又是怎么区分的?别急别急,这篇文章带你get所有数据在内存中的存储知识点。
1. 整数在内存中的存储
1.1 原码,反码,补码
对于整数,计算机有三种表示方式:原码,反码,补码。每种方式都是用二进制表示的,并且每种方式都是由两个部分组成:符号位与数值位。
无论哪种方式,符号位都是二进制序列中,从左向右数的第一位。符号位用1表示负数,0表示正数。正数的数值位都是相同的,但负数的每种方式的数值位都不相同,也就是说正数的原反补相同,负数的原反补不同。
原码
把一个数的十进制直接转换为二进制序列就是原码
反码
符号位不变,数值位按位取反得到反码
补码
反码加1得到补码
讲完理论,我们来看一个例子:10在内存中是怎样存储的
int main()
{
int num = 10;
//00000000 00000000 00000000 00001010 - 10的原码
return 0;
}
由于10是个正数,正数的原反补相同,所以10的补码就是原码。
一个负数在内存中又是如何存储的呢?
int main()
{
int num = -5;
//10000000 00000000 00000000 00000101 - -5的原码
//11111111 11111111 11111111 11111010 - -5的反码
//11111111 11111111 11111111 11111011 - -5的补码
return 0;
}
由于-5是负数,需要把-5的原码进行转化得到补码,只有补码才是负数在内存中的存储方式。
我们还能通过num的地址来观察num中存储的数据(一个整形有4个字节,也就是32个比特位,但在观察时,直接表示32个比特位过于复杂,而且也不好进行换算。所以编译器用十六进制来表示数据在内存中的存储,只是表现的形式!4个比特位表示一个十六进制数,这里的10表示为二进制是1010,十六进制为a)。但是我们发现10的存储顺序有点奇怪,不是00 00 00 0a,而是0a 00 00 00。这又是为什么?因为你的机器是小端存储模式。
1.2 大小端字节序
大端存储模式:数据的高字节位存储到低地址中,低字节位存储到低地址中。
小端存储模式:数据的低字节位存储到低地址中,高字节位存储到高地址中。
刚才观察的内存窗口中,地址从上往下是逐渐递增的,10的二进制是00 00 00 0a,把低地址的0a先储存到低地址中,所以0a在左边,接着是高字节序的00,以此类推。
再来个例子,直观感受小端存储模式。
我们直接把一个十六进制数11223344放进num中,按照小端存储模式,低字节序的44会先放到地址中,接着是33,22,11。编写程序,验证我们的说法
1.3 几个小练习
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a = %d, b = %d, c = %d", a, b, c);
return 0;
}
signed char 可以简写成char所以a与b的类型一样,-1的补码为11111111 11111111 11111111 11111111,储存在字符类型中要发生截断,截断后是11111111,打印时把补码转换成原码:10000001,结果就是-1。而c中的数据也是11111111,但是把这串二进制序列当作无符号数来解析,就不存在原反补的转换问题,所以把11111111转换成十进制就是255。
int main()
{
char a = -128;
printf("%u", a);
return 0;
}
-128的补码表示为11111111 11111111 11111111 10000000,储存到字符类型中要发生截断,截断后a中储存的值为10000000,再以无符号整形的形式打印,因为a是有符号的char,所以整形提升后为:11111111 11111111 11111111 10000000,把这串二进制序列当作无符号整形解析,
得到4294967168,编写程序验证
int main()
{
char a = 128;
printf("%u", a);
return 0;
}
128在截断后于-128的结果一样,都是10000000,之后的细节就于刚刚的那题一样,这里就不再赘述了
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d", i + j);
return 0;
}
i作为一个负数,是以补码的形式存储在内存中的,-20的补码为11111111 11111111 11111111 11101100,j的二进制序列是00000000 00000000 00000000 00001010,i + j的结果为11111111 11111111 11111111 11110110,转换为原码再转换成十进制是10000000 00000000 00000000 00001001,结果是-10。
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
输出先从9打印到0,接着i成为-1,-1的补码为11111111 11111111 11111111 11111111,但是这是一个无符号数,不存在原反补转换的问题,直接转换成十进制是一个42亿多的数字,始终满足i >= 0。
如果计算机会一直算,这个42亿多的数字会减到9,8,…一直到0,然后又是这个42亿多的数字,程序陷入死循环…
int main()
{
char a[1000];
int i = 0;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d",strlen(a));
return 0;
}
用strlen求字符串长度时,需要注意的一点是strlen遇到0停止,所以这一题要找哪个元素的值为0。我们知道char能存储的数据大小范围是-128 ~ 127,i从-1到-128,数组长度为128,-128-1 = 127,再从127到1,数组长度为127,之后就是0,所以数组长度为255。
int main()
{
unsigned char i = 0;
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
无符号字符类型的数值范围是0 ~ 255,当i = 255时再+1得到的数是0,因此循环无限进行。
如果对这两题还有不清楚的地方,建议看看这一篇博客关于数据类型存储范围的详解
2. 浮点数在内存中的存储
2.1 存储规则
根据国际标准IEEE(电气与电子工程协会)754规定,一个浮点数v可以表示成
v = (-1) ^ S * M * 2 ^ E
- 其中(-1) ^ S 表示符号位i,S = 0时,v为正数,S = 1时,v为负数
- M 表示有效数字,大于等于1,小于2
- 2 ^ E 表示指数位
单精度浮点数(32bit)中,最高位表示符号位,接着是8位指数位,最后是23位数字位。
双精度浮点数(64bit)中,最高位表示符号位,接着是11位指数位,最后是52位数字位。
对于有效数字M来说,由于1≤M<2,所以M又能写成1.xxxxxx的形式,M的第一位默认是1,这样首位的1就能舍去。所以IEEE 754规定,在存储M时只存储xxxxxx的内容,等到读取时再将1加上。
但是对于指数E来说情况就复杂了许多,E表示一个无符号整数,但是科学计数法中E可以是一个负数的。所以IEEE 754规定,E不全为0或不全为1,单精度浮点数存储E时,要加上一个中间数127,双精度浮点数存储E时,要加上一个中间数1023。
E全为0时,E的真实值为(1 - 127)或者(1 - 1023),并且M不再加上1,表示的数无限接近于0。
E全为1时,表示的数为无穷大或者无穷小。
说了这么多理论,举个例子:单精度浮点数5.5是怎么在内存中存储的?
把5.5转换成二进制是:101.1,可以表示为(-1) ^ 0 * 1.011 * 2 ^ 2。S = 0,M = 011,E = 2 + 127 = 129。
129的二进制是
这样我们得到5.5的二进制序列是0 10000001 01100000000000000000000
调试程序,验证浮点数在内存中存储的数字
通过验证可以看出我刚刚的推断是对滴
2.2 再来一个例子
int main()
{
int a = 7;
float* p = (float*)&a;
printf("%d\n", a); //表达式1
printf("%f\n", *p); //表达式2
*p = 7.0;
printf("%f\n", *p); //表达式3
printf("%d\n", a); //表达式4
return 0;
}
四个表达式打印的结果分别是什么?通过刚刚存储规则的讲解相信这个例子就不难了。
表达式1很简单,初始化一个整形为7,再以整形的格式打印,结果当然是7了。
但是表达式2呢?先写出7的补码:00000000 00000000 00000000 00000111,把p从整形指针强制转换成浮点型指针,解引用p时,p就会以浮点数解析它指向的内容。
根据刚刚的讲解,把7的补码以浮点数解析,我们得到S = 0,E = 0,M = 00000000000000000000111,而E全为0,所以表示的数无限接近于0。
由于p是一个浮点型指针,解引用p并改变p所指向内存空间的值时,p总是把数据当作浮点数来处理。7的二进制是111,可以表示成v = (-1) ^ 0 * 1.11 * 2 ^ 2,所以7用浮点数表示是0 10000001 11100000000000000000000,这个二进制表示的十进制是
与打印结果相同。
最后,对于数据在内存中的存储这一部分的笔记到此结束,希望这篇文章能对你有帮助