首先,我们需要知道C语言中数据的 基本内置类型,如下:
char //字符数据类型
short //短整型
int //整型
long //长整整
long long //更长的整型
float // 单精度浮点数
double // 双精度浮点数
对上述类型进行基本分类,如下:
//整型家族
char:
unsigned char
signed char
short:
unsigned short[int]
signed short[int]
int:
unsigned int
signed int
long:
unsigned long[int]
signed long[int]
//浮点数家族
float
double
此处需注意: 整型家族type前面不加signed,默认为signed type,例如int a = 1;其实就是signed int a = 1;
今天就讲整型/浮点数这两个家族在内存中的存储。
其实不管你什么类型,最终存到内存里都是二进制形态。关键在于如何得到这个二进制?且往下看
整型数据在内存中的存储
原、反、补
计算机中有符号数有三种表示方法:原码,反码,补码
这三种表示方法表示的数据均分为两部分:符号位 数值位
符号位只有1位:0表示正,1表示负
数值位三种方法不尽相同。
原码:将原数据按正负数直接转换为二进制,所得二进制即为该数据的原码
反码:原码符号位不变,数值位按位取反即得该数据的反码
补码:反码+1即得该数据的补码。
【注】:正数的原反补相同
对于整型家族而言,数据在内存中都是以其补码形态存放的。
以int a = 157;举个栗子:
157 = 128 + 16 + 8 + 4 + 1 = 2^7 + 2^4 + 2^3 + 2^2 + 2^0
首先,157是正数
157的原,反,补码:0000 0000 0000 0000 0000 0000 1001 1101
所以157在内存中就是上面的这个32位的二进制序列
再以int a = -1;举个栗子:
-1就是1加上符号,我们刚才已经说了,转原码时要按正负转,所以首先可以确定-1的原反补码符号位为1,符号位确定了,剩下的只需按正数规则来就行。
1 = 2^0
-1的原码:1000 0000 0000 0000 0000 0000 0000 0001
-1的反码:1111 1111 1111 1111 1111 1111 1111 1110
-1的补码:1111 1111 1111 1111 1111 1111 1111 1111
所以-1在内存中就是1111 1111 1111 1111 1111 1111 1111 1111
刚才我们知道了计算机中有符号数有三种表示方法及其如何转为补码,那计算机存储的是哪个呢?数据在内存中是以其补码形态存放的(对于整型家族而言)
为什么?有以下3点原因:
1.使用补码,数据的符号位和数值位可以统一处理;
2.CPU只有加法器,加法与减法可以统一处理;
3.补码与原码相互转换,运算过程相同,不需要额外的硬件电路
【注】现有有符号数的补码,如何得到其原码?
对于正数:原反补相同,不需要转换
对于负数:补码->符号位不变,数值位按位取反->+1即得到
栗子:
-1的补码: 1111 1111 1111 1111 1111 1111 1111 1111
符号位不变,数值位按位取反:1000 0000 0000 0000 0000 0000 0000 0000
加1: 1000 0000 0000 0000 0000 0000 0000 0001
即可得到-1的原码
那现在有符号数以怎样的形态存入内存我们已经知道了,无符号数呢?
无符号数就是没有符号的数,即正数,所以存无符号数,按有符号数中的整数来就可以啦。
有符号数与无符号数区别在哪里呢?以int与unsigned int 为例:
二者都是4字节,也就是共有32个比特位
有符号数将第一位当做符号位,只剩下31个数值位,所以有符号数的取值范围为[-2^31, 2^31-1]
而无符号数32位全是数值位,所以取值范围为[0,2^32-1]
大小端存储
现在我们已经知道整型家族在内存中是以补码形态存储的,可是内存中地址又分为高地址与低地址,二进制序列又有低位与高位,到底二进制的低位在内存中处于什么位置?高地址处or低地址?这就引出了大小端存储模式。
定义
大端模式:数据的低位存储在内存的高地址中,高位存储在低地址中
小端模式:数据的低位存储在内存的低地址中,高位存储在高地址中
为什么存在大小端模式?
举个生活中的栗子:
你和朋友从别的地方借了一架很高的梯子(梯子也有大小头),你抬得大头,朋友抬得小头。现在你们需要过一个门(有门框),现在必须一个人在前,一个人在后,谁先过这个门?(不准向螃蟹学习,横着过!哈哈)不论谁先过这个门,你们都能将梯子抬过去。对梯子来讲,可不一样,你先过去,梯子就是大头先过门,否则小头先过门。
回到大小端。在计算机中,字节为操作的基本单位,每一个地址单元都对应一个字节(8bit)。除了char(1字节)外。其他类型的数据想要存在内存中,就像抬梯子进门一样,怎么都可以存进去,但问题在于你把多个字节(高低位)怎么安排进去?这就有了大小端模式。
如何得知自己的计算机是大or小端模式?
十分简单,有以下两种方法:
1.【调试窗口——内存】查看
以int a = 157;为栗:
红框部分是内存地址,篮框是其对应存储的数据。可以从上图看到157的16进制低位0x9d被存在低地址0x8FFE5C,高位0x00被存在高地址0x8FFE5F。所以我的计算机是小端存储。如果是大端存储,0x9d应该在高地址0x8FFE5F,而0x00在低地址0x8FFE5C。
2.代码查看
#include <stdio.h>
int Check_Sys()
{
int i = 1;
//1的补码: 0000 0000 0000 0000 0000 0000 0000 0001
//字节划分:0000 0000 | 0000 0000 | 0000 0000 | 0000 0001
// 高位 ----------------------------> 低位
return (*(char*)&i);
//(*(char*)&i) : 将i的地址强转char *,再解引用,实现返回变量低地址空间(1字节)中的内容
// 如果大端存储,应该返回0,否则返回1
}
int main()
{
int ret = Check_Sys();
if (ret == 1) {
printf("小端存储\n");
}
else {
printf("大端存储\n");
}
return 0;
}
运行结果:
结果与【内存】窗口结果一致
【注】目前大多数计算机都是小端存储
浮点数在内存中的存储
根据国际标准IEEE754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S*M*2^E
(-1)^S表示符号位,s=0,V为正数;s=1,V为负数
M表示有效数字,1<=M<2
2^E表示指数位
举个栗子:
十进制5.0,写成二进制为101.0,按IEEE754标准表示为1.01*2^2。
根据规则可得:S = 0,M = 1.01,E = 2
IEEE754规定:对于32位的浮点数,最高位是符号位S,接着8位是指数E,剩下23位是有效数字M。

对于64位的浮点数,最高位是符号位S,接着11位是指数E,剩下52位是有效数字M。

IEEE754对有效数字M和指数E还有一些特别规定:
M的规定
前面我们说过1<=M<2,即M可以表示为1.xxxxx,既然小数点前都是1,我就不保存1了,只往内存中保存xxxxx。读取时,我再把1加回去就行了。这样M就可以存24个有效数字了????
本来只有23位没错⑧?现在我不保存个位1了,多于一个bit再保存x,是不是就24了。
eg:本来存1.xxxxx,现在存xxxxxx,读取时把1加回去,相当于保存的1.xxxxxx,是不是多了一位?没得问题哦
E的规定
E存入规定
E是一个无符号整数unsigned int且为8位,那么E的取值范围就是[0,255],11位则是[0,2047]。全是非负数,可是指数可以为负,怎么解决?
IEEE754规定,存入E的真实值时需要加上一个中间数(127 or 1023),127还是1023取决于E多少位(8 or 11)?
以E为8位为栗:
E的取值范围为[0,255],这是加上中间数127后的范围,那么E的真实取值范围必然为[-127,128]
E读取规定
1.E读取规定E不全为0/不全为1
从内存中读到E的计算值,减去127(1023),得到E的真实值,再给M加上第一位的1。
eg.
十进制:0.5,表示成二进制0.1,IEEE754规定形式:(-1)^0*1.0*2^(-1),E在内存中就是(-1+127)= 126,表示为0111 1110,M去去掉个位1为0,补齐23位为000 0000 0000 0000 0000 0000,则0.5在内存中是这样的
0 0111 1110 000 0000 0000 0000 0000 0000
2.E全为0
此时,E的真实值为-127(-1023),此时V的大小主要取决于E,而不再依赖M,但M又不可忽视。V极小(趋近0)没有任何问题,但由于M是不定的,所以V是接近0的一小段区域内的值,如下图。而这段区域就是浮点数的±0,也就是浮点数中的±0是一个区间,而不是一个值,因此我们编程时比较一个浮点数是否等于0这种行为是不对的。
3.E全为1
此时,如果M有效数字全为0,读取时,加上1,M真实值即为1.0。V将极大(正负取决于S),所以此时这种情况表示±∞

结束语
数据在内存中的存储问题大体已经讲完了,还有一些细节没有说,感兴趣的小伙伴可以自己去研究研究。比如以下问题:
1.为什么整型数据表示范围不对称?eg.char的表示范围[-128,127]
2.以%d打印浮点数据,以%f打印整型数据,结果为什么很奇怪?(这个今天去其实已经说了)
3.其他类型不匹配情况下的数据读取问题


被折叠的 条评论
为什么被折叠?



