目录
1.前言
我们都知道内存会根据类型给要存储的数据分配空间,那么数据在这块空间中是怎样存放的呢?我们都知道计算机只认识二进制数,它又是怎么处理各种不同类型的数据呢?
2.数据的类型
数据的类型大致可分为五类:整型、浮点型、自定义类型、指针类型和空类型,类型决定了存储数据能使用的空间大小。
2.1 整型家族
char、short、int、long、long long
2.2 浮点型家族
float、double
2.3 自定义类型
数组、结构体、联合体、枚举常量......
2.4 指针类型
*与变量名 结合即是指针的标志。
2.5 空类型
void 表示空类型(无类型) 通常应用于函数的返回类型、函数的参数、指针类型。
想要了解更多关于类型的信息可以查看库中 limit.h 和 float.h 的文件内容。
3.整型数据在内存中的存储
整型数统一转化成二进制数进行储存,如果 没有 强调是 无符号 类型,则每个空间内的第一个bit位会空出来作为符号位,1表示负数,0表示正数,剩下的位数用来表示整数部分,但数据还不是就这样存进去的。
3.1 原码、反码和补码
像上面说的那样转化出来的二进制数就称之为该数的原码,而单靠原码,正数和负数的运算则容易出问题,由于CPU中只有加法器(不要惊讶,事实就是这样)假设我们让十进制里的-1和1000想加,很显然结果是个正数,但是符号位上相加结果为1,计算机一识别就认为结果为负数,问题就出现了。
前辈研究出了反码和补码,有效地解决了这个问题,对于正数,其反码和补码的形式与原码一样;对于负数,其反码:保持原码的符号位不变,其它位按位取反;其补码:原码变反码后加一。这时我们用补码的形式对正数与负数相加,结果就为正确的了。
整型
3.2 大小端字节序存储方式
转化为补码后,计算机不能一次性读取一个整型的数据,只能一个字节一个字节地读取,计算机读取后,是把先读取的序列放在空间的高地址呢?还是低地址位呢?根据存储方式的不同,将高位数据,即先读取到的部分,放在高地址位,将低位数据,即后读取的放在放在低地址位,这样的存储方式叫做 小端字节序存储;将高位数据放在低地址位,低位位数据放在高地址位,这样的存储方式叫 大端字节序存储。
问:能否写一个判断编译器存储方式的代码?
4.浮点型数据在内存中的存储
猜猜下面代码执行的结果是:
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
结果是不是与预想中的结果不太一样,而且没有报错,原因就是浮点型小数不是像整型数那样的结构对数据进行存储的,采用不相符的格式解读会得到不一样的结果。
根据国际标准 IEEE(电气电子工程师学会)754规定,一个浮点型的数用以下形式表示:
(-1)^ S * M * 2^E
其中S为符号位,为1时代表是负数,为0时代表是正数;M为有效数字位,由于是二进制数,故非零浮点数的有效数字一定是以1.开头,所以在存储浮点数的有效数字时,会省略掉1.这一部分;E为指数位,注意这里的底数是2,不是10,IEEE 754规定E需要在其真实值的补码加上127(32位平台)或加上1023(64位平台)后再存入内存中。
E部分再被读取时,根据特殊的情况,计算机会采取特殊的策略读取:
如E中全为0时:此时易知这将会是一个十分小的数,所以计算机读取后会将E中数据+1后再减去127,+1是为了让数字整体向后移一位,从而不再在读取有效数字M部分后再在前面添1.xxxx,而是补0.xxxx以突出其十分接近无穷小。
当E中全为1时,此时可知这是一个十分大的数,如果选择将其输出,则不会打印其确切值,而是打印nan(为正时无符号,为负时有符号),表示其绝对值为一个十分大的数。
讲到这再回头看下开头提到的那段代码,将常量整型数9赋值给整型变量n,强制浮点型指针指向整型数,以整型的结构解读整型变量n,故正常输出整数9,虽然强制转换&n为float*,但指针类型只是决定了指针所能访问空间的大小,不会改变所取地址的变量的值,除非你解引用后进行赋值,故*pFloat指向的仍是9的补码:00000000000000000000000000001001,用%f解读的结果就是E部分全为0,就打印出0.000000,后面通过指针修改了相应空间内的值,将n变量内的前4个字节的数据改成9.0的浮点数的二进制序列,而变量n的空间也只有4个字节,这时n内的数据就为浮点数9.0的二进制序列:0 10000010 00000000000000000000000,再以%d解读,结果就是一个非常大的整数了,而用%f解读就是正常的结果。
5.补充
5.1 查看内存空间的信息
在VS上,开始调试后,在文件一栏找到调试,再点击窗口,再找到内存即可查看内存的详细信息。
5.2 数据解读问题
对于同一串二进制数,站在不同数据类型的角度解读,会被解读成不一样的数据,bug往往就会在这种地方出现,为了避免这种情况的发生,赋值时注意被赋值变量能正确读取的范围,除特殊需求外,指针的类型与被取地址的变量的类型保持一致,无符号数永远不会小于0,在循环条件中使用无符号数需要注意......
5.3 数据被误读的例子
5.3.1 格式不相符造成的误读
如用整型格式输出浮点型数时(将9改为了-9,其它同上面那段代码):
int main()
{
int n = -9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = -9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
这里我就只讲解一下nan,这个就是当E部分全为1时,计算机在识别后为表示其值之大,就用nan来表示了。
5.3.2 超出范围时的误读
高位截断造成的误读
int main()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
由于char类型的空间就8个bit,当值小于-256时就会对数据进行截断后,再存入,所以-256被截断后就将其后八位的00000000存入其中,于是strlen()的结果就是255。
精度提升时造成的误读
char n = 129;
printf("%d\n", n);
printf("%u\n", n);
这里的char 类型的空间只有8bit,能存储的范围是-128~127,这里的129就会被解读为-127,因为129的补码的后八个bit位和-127的补码相同, 直接截取后放在n的空间内就会被当成-127了,再用%d读取时,就输出-127了,那为什么%u读取的结果是这么大一个数呢?这是因为负数在进行整型提升时,高位补1造成的。
无符号数做循环条件时的错误样例
unsigned char i;
for (i=255; i>=0; i--)
{
printf("%u", i);
}
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
变量在做关系操作符的操作数时,按本身格式解读数据,而无符号字符类型只能读取0~255的整数,故上面的样例都出现了死循环。