#前言
不知各位在编写程序输出整型与浮点型数据时,是否有考虑过他们在内存中的存储方式呢?又是否在以不相对应的类型输出时出现过自己难以想象的值呢?这其实都和它们在内存中的存储方式有关。
#整形数据的存储
我们都知道数据在内存中是以其二进制补码的形式存储在内存中的,但是不同的数据类型有着不同的存储字节的空间,比如,整型为4个字节,字符型为1个字节(32位)。那么如果我们将一个整型的数据放入一个字符型的空间中会发生什么呢?
在上图中我们可以看到,当a = 128时可以正常打印但是当b = -129时却打印出了127,这是为什么呢?其实原因很简单,因为在32位下,字符型占一个字节空间,其能正确表示的范围是-128-127,而-129显然超出了范围故不能正常打印。但是为什么能打印出127呢?是巧合吗?当然不是,接下来我们就来研究一下为什么输出的是127。我们可以知道-129的二进制原码为:0 1000 0001(其符号位为1),反码为:1 0111 1110,补码为:1 0111 1111,但是当其放入字符空间时会被截断导致其只保留8个二进制为,则此时其补码为:0111 1111,又其最高位是0故其被解析成正数,正数的原码反码补码相同,将其转化为10进制则为127。
搞懂了第一段代码接下里我们再看一个例子,此时为什么打印出的结果却又不同呢?因为此时%u将存储的二进制补码解析为正数打印,-128的补码为:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000 0000 ,此时将其截断为1000 0000,又将其用%u输出时会发生整形提升,此时符号位为1,故其提升后为1111 1111 1111 1111 1111 1111 1000 0000,有%u将其以无符号数打印,故为
#浮点数的存储
浮点数的存储方式比起整型复杂了不少,具体存储方式如下:
其中(-1)^S表示符号位,当S为0时为正数,为1时为负数;M代表有效数字大于1小于2(M存入内存时只保留小数点后的数字,而将1去除);2^E代表指数位。例如。整数5.5,其二进制表达方式应为101.1,故可将其写成(-1)^0*1.011*2^2(因为将小数点向左移动两位所以指数位为2,因为是二进制所以底数为2)。但是这种做法是有一定缺陷的,因为指数E是可能出现负数的,那么此时可能会与S所代表的符号位发生冲突。所以在存储规范中规定了一个中间值,存入内存时的E都必须加上这个中间数,在32位浮点数中中间数位127,对于64位则为1023.比如,2^10的E是10,所以保存为32位浮点数时,必须保存为10+127=137,即10001001,在输出时在减去相应的中间数。接下来我们来看一个例子:
#include<stdio.h>
int main() {
int n = 9;
printf("%f", n);
return 0;
}
那么此时输出的数是多少呢?根据我们之前分析的浮点数在内存中的存储方式可知9在内存中的二进制补码的第一位为S,而后8位为E,最后11位为M,则如下图:
注意此时E的值为全0,此时为一种特殊情况,这是浮点数的指数E等于1-127(或者1-1023),有效数字不在加上第一位的1,而是还原为0.xxxxxxx的小数,这样做是为了使其成为接近于0的小数用与表达0,所以可知此时输出的值为(-1)^0*M*2^-126这样一个非常小的数,所以将会打印出0。
那么我们接下来在讲一个例子:
此时则是将9.0这个浮点数存入内存后以整型方式打印,那么我们可知9.0,表示成二进制为1001.0 ,其可写成(-1)^0*1.001*2^3,故可知其在内存中存放的形式为(M=1.001,存入内存时将1去除只存入001,位数不够则补0):
故将其以整数形式打印则可得出:
则可验证打印出的10进制数与编译器输出的相同。