在数据存储中,整数与浮点数在内存中的存储方式有很大的差别,下面我们将分别探讨它们的存储方式。
目录
1.整数在内存中的存储
2.大小端字节存储和字节序判断
2.1何为大小端?
2.2设计程序判断机器的字节序存储方式
3.整型提升与补码的深层含义
3.1 在c语言中,整型算术运算总是至少以缺省整型类型(int / unsigned int)的精度来进行的。为了获得这个精度表达式中字符型(char)和短整型(short)操作数在使用之前被转换为普通整型,即为整型提升(对与补码而言)。
3.2补码的一些理解
4.浮点数在内存中的存储
4.1先看一段代码:
4.2浮点数存储过程
整数的二进制表示形式有三种:原码,反码和补码。
这三种表示方式均由符号位和数值位两部分组成,符号位为最高位,0为”正“,1为”负“,其余位均为数值位。
原码:直接将数值按照正负数翻译成二进制数得到的就是原码;(人的思维上所想的数)
反码:原码符号位不变,其他位按位取反;(0变1,1变0)
补码:反码基础上+1;
正整数的原码,反码,补码都相同。
而负整数的原码,反码,补码都不相同;
//5的原,反,补码:
原:0000000000000000000000000000101
反:0000000000000000000000000000101
补:0000000000000000000000000000101
//-5的原,反,补码:
原:1000000000000000000000000000101
反:1111111111111111111111111111010
补:1111111111111111111111111111011
那为什么有原码了还要有反码,补码呢?
这是因为在计算机系统中,数值都是用补码形式来表示和储存的。
那为什么是补码形式呢?
原因是使用补码可以将符号位和数值域统一处理;加减法也可以同一处理(cpu只有加法器);原码反码相互转换其运算过程也是相同的(均为取反加一),不需要额外的硬件电路。
当一个数值的内存大小超过一个字节时,就得考虑一个问题,既然地址分高低,那我数值的低位存在内存的低地址,还是存在内存的高地址中?于是就有了存储顺序的问题,按照不同的存储顺序,将其分为大端字节序存储和小端字节序存储:
大端(存储)模式:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处。(低位存高址)
小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。(低位存低址)
为什么会有大小端之分?
历史原因:在计算机发展的早期阶段,不同的处理器厂商采用了不同的字节序,导致在数据交换和通信时出现了问题。为了解决这个问题,需要有大小端之分。
数据交换:在进行数据交换或通信时,如果双方的字节序不一致,就需要进行字节序的转换,否则会导致数据解析错误。
硬件兼容:不同的硬件架构可能采用不同的字节序,为了保证软件在不同平台上的兼容性,需要考虑大小端问题。
总之,大小端之分主要是为了解决不同处理器和硬件架构之间的兼容性和数据交换的问题。
//方法1
#include
int check_sys(void)
{
int i = 1;
return (*(char*)&i);
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
//方法二,运用联合体的特性
#include
int check_sys(void)
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
对于联合体的方法暂时不理解的可以看我下一章,会详细讲解联合体。
double,float(先提升为double),long long运算时为8个字节。
如何整型提升?
a. 在补码的基础上,有符号补符号位
b.无符号补0
#include
int main()
{
unsigned char a = 100;
unsigned char b = 200;
unsigned char c = a + b;
printf("%d %d\n", a + b, c);
return 0;
}
大家可以自己先思考一下,答案会是什么?
接下来进行分析:
首先unsigned char类型所能表示的数值范围是0~2^8-1,即为0~255(二进制:11111111),那么a与b没有超出取值范围,所以第一个打印的是300(100 + 200);再来看变量c,变量a和b的和300赋值给变量c,而变量c取值范围容不下300,那就要发生截断:
//300
原码:000100101100
反码:000100101100
补码:000100101100
//c为char类型,8个比特位
发生截断:00101100(补码)--44
细心的小伙伴会发现,44 + 256 = 300,这是巧合吗?
补码的局限性:补码只能表示有限的数值范围,对于超出范围的数值,计算机会出现溢出错误。此外,补码也无法表示负零和无穷大等特殊数值。
数据是以二进制补码进行存储和运算的,不同数据类型所能表示的范围不一样,举个例子:
signed char:-128~127;unsigned char:0~255;
对于signed char是由符号的,所以二进制最高位得留给符号位,当最符号位为0时(+),所能表示范围为0~127(2^7 - 1)包括0在内共128个数;当最高位为1时(-),理论上来说应该是-0~-127同样128个数,但是0就是0,无正负之分,既然0已经划分到0~127去了,负数范围为了也能同样表示128个数值,那就变成了-1~-128,总体上就为-128~127。
对于unsigned char是无符号类型的,自然最高位就不用给符号位留位子,那取值范围为0~255(2^8 - 1).
那补码要如何有规则地处理这些数据呢?这里可以把它看成一个时钟,亦是一个轮回。
还是以signed char为例:
由整数0开始(补码00000000)直到127(01111111),127加一是不是就是128了?其实不是,但是有联系,128补码为10000000,但是是signed char类型,所以最高位的1会被当成符号位表示负数,那是不是表示0呢?也不是,因为这是负数的补码,要转换成原码才找到它表示多少,10000000取反加一,得到00000000,跟上面解释的一样不存在-0这种表示形式,那么这里的00000000被表示为-128,-128加一-127……直到-1加一变为0,0加一等于1……,不断循环往复,不断进行轮回。
#include
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;
}
根据代码运行结果,小伙伴可能会发生这样的疑惑:当n为整型时,将其整型指针强转为浮点型指针,使指针指向为浮点型,解引用时结果为什么会和整型不一样?当n为浮点型时,结果为何又有差异?有些小伙伴可能已经想到了,既然值不同,那就说明其在内存中存储的形式不一样。
想法是正确的,浮点数的存储方式与整型的存储方式的确不同。
整型的存储方式前面已经讲过了(忘了的快回头!),现在讲讲浮点数的存储方式:
根据IEEE754(电气电子工程师学会)规定,任意一个二进制浮点数V可以表示成下面的形式:
V = (-1)^S * M * 2^E
其中(-1)^S表示符号位,当S=0时,V为正数,当S=1时,V为负数
M表示有效数字,M是大于1小于2的
2^E表示指数位
举个例子,十进制的6.0,二进制为110.0,根据上面规则即为110.0=(-1)^0 * 1.10 * 2^2
S=0;M=1.10;E=2.
十进制的-6,二进制为-110.0,即为-110.0=(-1)^1 * 1.10 * 2^2
S=1;M=1.10;E=2
既然任意浮点数能用这三个变量表示,那么内存中存放这三个变量的信息不就行了?没错,是这样的。
IEEE754规定:
回到前面那段代码:
9在整型中二进制补码存储为00000000000000000000000000001001
将00000000000000000000000000001001拆分为浮点数二进制补码存储形式:
0 00000000 00000000000000000001001
S=0;E=1-127;M=00000000000000000001001;
显然,E为全0,表示一个很小的正数;
看第二环节,将n以浮点数二进制存储9.0时,即S=0;M=1.001;E=3;
那么内存表示为0 10000010 00100000000000000000000
即为01000001000100000000000000000000
上面二进制数被当作整型时表示
以上就是这一期的全部内容,如果对你有所帮助的话,不要忘记点赞,收藏加关注,如果文章存在错误或者需要补充,欢迎评论区留言,我们下期再见!!!