目录
简介数据在内存中的存储方式
整形
有符号整形(signed)
无符号整形(unsigned)
原码、反码、补码
大端小端
整形提升
数据截断
浮点数在内存中的存储
S、E、M
S
M
E
double和float的存储模型
简介数据在内存中的存储方式
在讨论数据在内存中的存储方式之前,我们需要先确定一个概念,数据存储在内存中的基本单位是字节
c语言类型分类如图:
一、 自定义类型不在我们讨论范围之内
原因是:自定义类型是由用户来定义的,用户定义的不同,它存储的大小就不同,它并不是固定的
二、指针类型我们不重点说明
原因是,所有的指针存储的都是一个地址,在32位机器下它是占用四个字节,在64位机器下它是占用8个字节
指针一句话概括:我们可以把指针看作一个整形类型,但里面存储的一定是一个地址,指针的大小取决于你地址的长度!
那么现在让我们看看内置类型中整形和浮点型
整形
所有的整形家族中,都分为无符号的整形(unsigned)和有符号的整形(signed)
默认情况下如果我们不显式写signed/unsigned,默认是signed
注意:这一点char类型有点特殊,在c语言标准中没有说明char类型默认是signed/unsigned,这个是由不同的编译器实现的。
把一个小于int的类型(char/short)写在表达式计算/通过整形输出的时候,就会发生整形提升的概念
把一个较大类型存储在比他小的类型当中时,那么就会发生数据截断的概念
注意:这里的大小指的是开辟空间的大小,也就是所占字节的个数大小!
有符号整形(signed)
在我们进行数据存储时,二进制的有符号整形通常最高位是表示正/负的
如果数值为正数,那么最高位则为0,反之则为1,这个最高位我们叫做符号位,其他的我们叫做数值位,所谓有符号的整形可以理解为有正负之分的整形。
注意:虽然有符号的整形存储在内存中的最高位是用来表示正负的,但在进行计算时,符号位也还是会用来计算
signed表示的就是有符号的整形
无符号整形(unsigned)
所谓的无符号整形我们可以理解为:在有符号的整形的基础上把最高位的符号位转化为数值位。我们可以想象,如果没有了符号位,这个数就永远 ≥ 0
原码、反码、补码
学习了前面的有符号和无符号,相信我们对于一个正负数怎么区分也有了一个概念
接下来请思考一下:
-2+1=?
-2的二进制序列: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
1 的二进制序列: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
-2+1 = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 = -3
可以看到,结果似乎并不正确!
同样的,科学家也有跟我们一样的疑惑,负数的+好像并不正确
其实上面的序列我们写的是两个原码序列,由上可以看出原码序列不能直接用来存储
于是就出现了反码,反码的定义是正数不变,负数符号位不变其他位按位取反
再来看看上面的问题
-2的二进制反码: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1
1 的二进制反码: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
-2+1 = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
符号位不变其他位按位取反:
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1=-1
我们惊喜的发现好像就成功了,事情就这么简单吗?
显然不是,不然标题就取原码、反码就可以了,要补码干嘛
我们再看看-1+1,按照上面的方法再取一遍试试
-1的二进制反码 = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
1 的二进制反码 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
-1+1 = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
符号位不变其他位按位取反
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 = -0
由于-0和0是一个数却对应了两个二进制序列,这不符合二进制的唯一性,于是反码也不能用来计算了,但我们发现上面的反码在某些特殊情况下才会出现错误
于是科学家在反码的基础上改造出了二进制补码,这也是最终整形在内存中存储的方案
补码的定义是:负数的补码在反码的基础上+1,正数还是不变
我们再计算一下上面的-1+1
-1的二进制补码 = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1的二进制补码 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
-1+1 = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
可以看到由低位到高位第33位是1,默认的整形只能存储32位所以第33位没有存储进来,最终
-1+1 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 = 0
高位为0,补码=原码
结果正确
补码最妙的地方是他能依靠一套逻辑硬件完成这一整套过程(获取补码和转化为原码)
原码 = 补码取反+1
补码 = 原码取反+1
这里大家不妨试试!
大端小端
大端和小端就是计算机在存储数据时的顺序问题!
大端和小端顺序问题的基本单位是字节,即1个字节的类型(char)不存在大端和小端之分
大端:计算机存储数据时低地址存储高位数据,高地址存储低位数据
小端:计算机存储数据时低地址存储低位数据,高地址存储高位数据
如:1 = 00000000000000000000000000000001
此处最右边为最低位,最左为最高位
“1”在小端机中存储:
←低地址 高地址→
0x01 00 00 00(16进制)
“1”在大端机中存储
0x00 00 00 01(16进制)
为什么会出现大小端?
在以前人们在定数据存储的时候有许多许多的存储方式,而数据怎么存其实不重要,只要能存进去后拿出来即可,也就出现了各种各样的存储方式,最终筛选出来的就两种,大端和小端
因为这两种是最好取出来的方式!
验证机器大小端:
思路:我们只需要固定一个变量=1,再取出地址,使用char*指向这个地址,再解引用一次访问一个字节,而高字节如果是0就是大端,是1即为小端
#include<stdio.h>
bool CheckPort()
{
int a = 1;
return *(char*)&a;
}
int main()
{
printf("%d",CheckPort());
return 0;
}
整形提升
把一个小于int的类型(char/short)写在表达式计算/通过整形输出的时候,就会发生整形提升的概念
整形提升:
1、有符号的整形:
如果转化为二进制不满足32位且符号位为0,那么就在符号位补0,补够32位为止
如果转化为二进制不满足32位且符号位为1,那么就在符号位补1,补够32位为止
2、无符号的整形:
如果转化为二进制不满足32位,则在高位补0,补够32位为止
为什么会发生整形提升?
因为表达式计算和整形输出时,要把这个数据放到cpu的相关整形运算器中,而一般整形运算器的标准都是4字节的倍数,且寄存器大多也是4字节的倍数,如果我们要两个char之间进行运算,消耗是很大的,而整形提升则可以把所有类型的整形都转化为统一的4字节进行处理,那么就用一套相关整形运算器完成了所有类型的统一计算!
如下代码
#include<stdio.h>
int main()
{
char a = -128;
//a原码 = 110000000;
//a反码 = 101111111;
//a补码 = 110000000;
//因为a是char类型的所以a里面最多存储一个字节,那么最高位的1就被丢了
//最终a存储 = 10000000
printf("%u",a);
//此处要输出a,发生整形提升,高位是1
//最终输出二进制为:11111111111111111111111110000000 的数
return 0;
}
数据截断
数据截断其实在前面的内容中也发生过很多次了,数据截断是一个较小的类型存储一个较大的类型时发生的,例如char类型存储int类型的值
规则:从低位开始取,取出的数据大小与较小类型大小相同,而多出来的数据直接丢弃
例如:
0x11 22 33 44 要存储在一个char类型里
char类型所占空间为一个字节,那么取出数据中的最低位一个字节
则char类型中存储的就是0x44
浮点数在内存中的存储
S、E、M
根据IEEE5(电气电子工程师协会)标准规定:浮点数在内存中的存储的是SEM
无论哪一个浮点数的在内存中表示形式都能转化为:(-1)^S * M * 2^E
可以看到,此表达式中变量为S、M、E,也就是只要我们知道了S、M、E就能还原出一个浮点数
注意:浮点数在内存中存储的时候小数点后面的数通常转化为2^x,x<0
例1:
“6.5”转化为二进制后
错误:110.101(小数点后的数不能用二进制整形的形式表示)
正确:110.1(解读为:2^2 + 2^1 + 2^-1)
例2:
"7.625"转化为二进制后
111.101(解读为:2^2 + 2^1 + 2^0 + 2^-1 + 2^-3)
S
S可以用来表示一个浮点数的正负,他只有为0和1两种形式
如果S = 0,那么-1的0次方即为1,那么这个浮点数即为正数
如果S = 1,那么-1的1次方即为-1,那么这个浮点数即为负数
M
M指的是浮点数的有效数据,这个数据是>=1 && < 2 的,再取出这个数的小数位即为M
例如
"6.5"转化为二进制后为110.1
我们把他转化为 >=1 && <2的数据后(小数点要左移两位)
1.101
而此处的有效数据即为1.101
M存储的就是这个小数点右边的数据(101)
注意:M只存储小数点后的数据的原因是因为这个数据的小数点左边一定是1,那么我们就不用存储固定的值,在取出这个M后自动在前面补上一个1就可以了
E
E指的是,在我们把浮点数的数据转化为 >=1 && <2 后移动的位数,再+中间值后即为E
左移为正,右移为负
如果我们把这个数左移了两位,那么真实E = 2
如果我们把这个数右移了两位,那么真实E = -2
中间值:在存储浮点数时表示E的类型通常为无符号的,E却可能为负,这时的解决办法就是加上中间值让他正数和负数都存储成正数,最后当我们要取出数据时直接减去中间值就得到了它的真实值
float:因为在float中存储E的空间为8个bit位,且无符号(范围0-255),标准规定这个中间值为127,也就是所有的float类型中存储的E都是加上了127,真实E为存储的E-127
double:double类型中,E的存储空间为11个bit位,也是无符号的(范围0-2047),标准规定这个中间值为1023,也就是所有的float类型中存储的E都是加上了1023,真实E为存储的E-1023
double和float的存储模型
好啦,今天的内容就到这啦,感谢大家的支持,我们下期再见