数据在内存中存储
- 一、整数在内存中的存储
- 二、大小端字节序和字节序判断
- 2.1什么是大小端?
- 2.2练习
- 2.2.1练习1
- 2.2.2练习2
- 2.2.3练习3
- 2.2.4练习4
- 2.2.5练习5
- 2.2.6练习6
- 三、浮点数在内存中的存储
- 3.1练习
- 3.2浮点数的存储
- 3.2.1 浮点数存的过程
- 3.2.2 浮点数取的过程
- 3.3题目解析
- 四、总结
- 4.1整数在内存中的存储
- 4.2大小端字节序
- 4.3浮点数在内存中的存储
- 4.4总结
一、整数在内存中的存储
整数的2进制表示方法有三种,即原码、反码和补码。
有符号整数,符号位 0 —— 正 1 —— 负,最高位为符号位
正整数的原、反、补码都相同。
负整数的三种表示方法各不相同,如下:
原码:直接被数值按照正负数的形式翻译成二进制得到的是原码。
反码:将原码的符号位不变,其他位次按位取反,就得到了反码。
补码:反码+1就得到补码。
注:对于整形来说:数据存放内存中其实存放的是二进制补码。
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。 原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
二、大小端字节序和字节序判断
当我们了解了整数在内存中存储后,我们调试看一个细节:
int main()
{
int n = 0x11223344;
return 0;
}
调试结果如下图:
注:
1.整数在内存中存储的是二进制的补码。
2.在调试窗口中观察内存的时候,为了方便展示,显示的是16进制的值。
3.存储的顺序是倒过来的!
进行调试后,观察上述图片我们可以看到n中的 0x11223344 这个数字是按照字节为单位,倒着存储的。这是为什么呢?
2.1什么是大小端?
x86 —— 小端模式(DSP) KEIL C51 —— 大端模式
其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储。
大端字节序存储:把一个数据的低位字节的内容存储到高地址处,把高位字节的内容存储到低地址处。
小端字节序存储:把一个数据的低位字节的内容存储到低地址处,把高位字节的内容存储到高地址处。
如下图:
为什么有大小端模式之分?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit 位,但是在C语言中除了8 bit 的char 之外,还有16 bit 的 short 型,32 bit 的 long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致大端存储模式和小端存储模式。
2.2练习
2.2.1练习1
设计一个小程序来判断当前机器的字节序:
#include <stdio.h>
int main()
{
int n = 1;
if(*(char*) & n == 1)
printf("小端\n");
else
printf("大端\n");
//0x00 00 00 01
//01 00 00 00
//00 00 00 01
return 0;
}
对于if语句阔号内的解析:(把下面的A换成n即可)
运行结果如下图:
2.2.2练习2
signed char a;//有符号的;char —— 1个字节 —— 8bit位。
unsigned char b;//无符号的; 2^8 —— 256。
如果是signed char 类型,那么内存中的最高位就被当做符号位。
signed char类型的取值范围是-128~127;对于unsigned char的取值范围是 0 ~ 255。
代码2:
#include <stdio.h>
int main()
{
//char到底是有符号的char还是无符号的char是取决于编译器的!
//在VS上char == signed char
char a = -1;
//10000﹍﹍0001
//11111﹍﹍1110
//11111﹍﹍1111
//11111111 —— a
signed char b = -1;//整型提升
//11111111 —— b
unsigned char c = -1;
//11111111 —— c
printf("a = %d,b = %d,c = %d",a,b,c);
//-1 -1 255
return 0;
}
运行结果如下图:
2.2.3练习3
代码3:
#include <stdio.h>
int main()
{
char a = -128;//1000﹍﹍10000000 —> 1111﹍﹍01111111
printf("%u\n",a);//?4294967168//—> 1111﹍﹍10000000
//%u的形式打印,是认为a中存放的是无符号数//10000000 —— a//整型提升
//a是char类型,首先要整型提升
return 0;
}
运行结果如下图:
代码4:
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);//4294967168
return 0;
}
运行结果如下图:
2.2.4练习4
代码5:
//signed short -32768 ~ 32767
//unsigned char 0 ~ 65535
#include <stdio.h>
int main()
{
char a[1000];//0 ~ 999
int i;
for(i = 0;i < 1000;i++)
{
a[i] = -1-i;
}
//-1 -2 -3 -4 ﹍﹍ -127 -128 127 126 ﹍﹍ 4 3 2 1 0 -1 -2 ﹍﹍
printf("%zd",strlen(a));//求的是字符串的长度,统计的是\0(ASCII码值是0)之前的字符个数
//255
return 0;
}
运行结果如下图:
2.2.5练习5
代码6:
#include <stdio.h>
unsigned char i = 0;//unsigned char 的取值范围是0 ~ 255
int main()
{
for(i = 0;i <= 255;i++)
{
printf("hello world\n");//死循环打印
}
return 0;
}
运行结果如下图:
代码7:
#include <stdio.h>
int main()
{
unsigned int i;
for(i = 9;i >= 0;i--)//死循环
{
printf("%u\n",i);
}
return 0;
}
运行结果如下图:
2.2.6练习6
代码8:
#include <stdio.h>
//X86环境小端字节序
int main()
{
int a[4] = {1,2,3,4};//1.指针+1,取决于指针的类型;2.整数+1,就是+1。//&a —— int(*)[4]
int*ptr1 = (int*)(&a + 1);
int*ptr2 = (int*)((int)a + 1);
printf("%x,%x",ptr1[-1],*ptr2);//4 2000000
return 0;
}
三、浮点数在内存中的存储
浮点数:float、double、long double类型
浮点数表示的范围:float.h中定义;整数的取值范围:limits.h中定义。
3.1练习
练习代码如下:求其运行结果
#include <stdio.h>
int main()
{//说明:整数和浮点数在内存中的存储方式是不一样的。
int n = 9;
float*pFloat = (float*)&n;//int*
printf("n的值为: %d\n",n);//9
printf("*pFloat的值为: %f\n",*pFloat);//0.000000
*pFloat = 9.0;
printf("n的值为: %d\n",n);//1091567616
printf("*pFloat的值为:%f\n",*pFloat);//9.000000
return 0;
}
运行结果如下图:
3.2浮点数的存储
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:
V = (-1)S * M * 2E
(-1)S表示符号位,当S = 0,V为正数;当S = 1,V为负数。
M表示有效数字,M是大于等于1,小于2的。
2E表示指数位
如下图所示:
注:所以浮点数的存储,其实存储的就是S,M,E相关的值。
IEEE 754规定:
对于32位的浮点数(float),最高的1位存储符号位S,接着的8位存储指数E,剩下的32位存储有效数字M。
对于64位的浮点数(double),最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M。
3.2.1 浮点数存的过程
IEEE 754对有效数字M和指数E,还有一些特别规定:
1 <= M <2,M可写为1.xxxxxx的形式,其中xxxxxx表示小数部分。
IEEE754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分,比如:保存1.01时,只保存01,等读取时,再把第一位的1加上,这样做的目的是节省1位有效数字。
至于指数E,情况比较复杂:
首先,E为一个无符号整数(unsigned int) E为8位,0 ~ 255;E为11位,0 ~ 2047。
我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023.比如,2^10的E是10,所以保存32位浮点数时,必须保存成10+127=137,即10001001。即使是浮点数也是有最大值和最小值。
E也是有最大值和最小值的, E+127 E+1023
int main()
{//5.5
float f = 5.5f;
//5.5 —> 101.1 —> 1.011*2^2 —> (-1)^0*1.011*2^2
//S = 0 M = 1.011 E = 2
//2 + 127 = 129
//01 00 0000 1011 00000000000000000000
//40 B0 00 00
return 0;
}
3.2.2 浮点数取的过程
指数E从内存中取出还可以分成三种情况:
E不全为0或者不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或者1023),得到真实值,再将有效数字M前加上第1位的1。
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0,xxxxxx的小数,这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号S)
3.3题目解析
代码如下:
#include <stdio.h>
int main()
{
int n = 9;
float*pFloat = (float*)&n;//int*
printf("n的值为: %d\n",n);//9
printf("*pFloat的值为: %f\n",*pFloat);//0.000000
//站在pFloat的视角,它会认为自己指向的是1个float类型的数值,当内存中的E为全0的时候,真实的E:1-127 —> -126 有效数字M,取出后不再加上第1位的1
//(-1)^0*0.0000﹍01001*2^(-126) ≈ 0
*pFloat = 9.0;
printf("n的值为: %d\n",n);//1091567616
printf("*pFloat的值为: %f\n",*pFloat);//9.000000
return 0;
}
图文解释:
四、总结
本文详细讨论了整数和浮点数在内存中的存储方式以及相关的字节序和浮点数表示方法。文章主要涉及了三部分内容:整数在内存中的存储,大小端字节序的判断与实现,以及浮点数在内存中的存储和转换。
4.1整数在内存中的存储
文章首先介绍了整数在内存中的存储方式。整数使用原码、反码和补码三种形式表示,其中,原码是最直接的表示方式,反码是通过对原码符号位之外的所有位取反得到的,而补码则是在反码的基础上加1得到。补码在计算机中被广泛应用,因为它不仅能有效表示正负数,还简化了加法和减法操作。计算机中所有整数的存储都是以补码的形式进行的,这样可以统一处理符号位和数值域,并且不需要额外的硬件支持,这使得计算更加高效。
4.2大小端字节序
文章进一步探讨了大小端字节序的问题。在计算机系统中,超过一个字节的数据如何存储在内存中,会受到字节序的影响。字节序分为大端和小端两种模式。在大端字节序中,数据的高位字节存储在低地址处,低位字节存储在高地址处;而在小端字节序中,低位字节存储在低地址处,高位字节存储在高地址处。这种存储顺序的差异,主要是由于计算机处理器的寄存器宽度大于一个字节,导致了如何安排多字节数据的问题。文章还通过代码示例,展示了如何判断当前系统的字节序。通过访问内存中的字节,判断数据的存储顺序,从而确定系统使用的是大端还是小端字节序。
4.3浮点数在内存中的存储
接着,文章讲解了浮点数在内存中的存储方式。浮点数采用IEEE 754标准来表示,在此标准下,浮点数由符号位、指数部分和有效数字部分组成。对于32位的浮点数(float类型),符号位占1位,指数占8位,有效数字占23位。对于64位的浮点数(double类型),符号位占1位,指数占11位,有效数字占52位。浮点数的表示使用科学计数法,其中符号位表示正负,指数部分表示数字的范围,有效数字则表示具体的数值。通过这种表示方法,浮点数能够有效表示非常大或非常小的数值,且能够处理精确的小数部分。
浮点数的存储与整数有所不同,尤其是在对存储内容的解读时,浮点数的指数和有效数字需要特殊处理。例如,当浮点数的指数部分全为0时,它表示的是接近于0的非常小的数字,而不是普通的0。对于有效数字部分,IEEE 754标准规定了它的存储方式,即默认保留第一位为1,因此只存储后续的小数部分。这些存储规则有助于节省内存空间,同时确保浮点数的精度和范围。
4.4总结
通过本篇文章,我们了解了整数和浮点数在内存中的存储原理,以及大小端字节序的差异。对于整数,补码的存储方式提供了一个简洁高效的处理方案,能够统一处理符号和数值域。大小端字节序的存在是由于处理器寄存器的宽度大于一个字节,在不同的系统中会有不同的存储顺序。浮点数的存储则遵循IEEE 754标准,通过符号位、指数和有效数字来表示浮动的数值范围。所有这些知识对于理解计算机内部的数据存储和处理方式至关重要,尤其是当我们需要优化程序或进行低级编程时。