数据在内存中的存储
一.整数在内存中的存储
- 我们之前在操作符里面就提到过,整数的2进制表示方法有三种,即原码,反码和补码。详细请看:操作符------原码反码和补码
注意正整数的原,反,补码都相同;负整数的三种方法各不相同 - 那么对于整数来说,数据存放内存中其实存放的就是二进制的补码 的原因如下:
使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是不相同的,不需要额外的硬件电路
二.大小端字节序和字节序判断
1.大小端
超过一个字节的数据在内存中存储的时候就有存储顺序的问题,按照不同的存储顺序,我们分为大端字节序存储和小端字节序存储。用图举个例子:
- 大端(存储)模式:是指数据的低位字节内 (如上图中的6) 保存在内存的高 地址处,而数据的高位字节内容 (如上图中的1)保存在内存的低地址处
- 小端(存储)模式:是指数据的低位字节内容 保存在内存的低 地址处,而数据的高位字节内容 保存在内存的高 地址处
如下面在内存的存储就是小端字节序存储
2.大小端存在的原因
计算机系统是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit位。但是C语言中还有16bit的short型等,另外对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大小端存储模式
3.例题
- 设计一个小程序来判断当前机器的字节序:
c
#include<stdio.h>
int check_sys()
{
int a = 1;
return (*(char*)&a);
}
int main()
{
if (check_sys()==1)
{
printf("大端\n");
}
else
{
printf("小端\n");
}
return 0;
}
- 代码打印结果
我们首先需要知道一个知识点:
signed char的取值范围:-128~127
unsigned char的取值范围:0~255
同理:
signed short(16bit)取值范围:-32768~32767
unsigned short:0~65535
c
#include<stdio.h>
int main()
{
char a = -1;
//原:10000000 00000000 00000000 00000001
//反:11111111 11111111 11111111 11111110
//补:11111111 11111111 11111111 11111111
//保留低位:11111111 -a
//整型提升:11111111 11111111 11111111 11111111
signed char b = -1;
unsigned char c = -1;
////原:10000000 00000000 00000000 00000001
//反:11111111 11111111 11111111 11111110
//补:11111111 11111111 11111111 11111111
//保留低位:11111111 -c
//整型提升(补码):00000000 00000000 00000000 11111111
//对应255
printf("a=%d,b=%d,c=%d", a, b, c);//%d是以十进制的形式打印有符号的整数
return 0;
}

- 求代码打印结果
%u是以十进制的形式打印无符号的整数------内存中存储的是无符号数的补码
c
#include<stdio.h>
int main()
{
char a = -128;
//原10000000 00000000 00000000 1000000
//反11111111 11111111 11111111 0111111
//补11111111 11111111 11111111 1000000
//按低位截断:10000000-a
//按原来的类型进行整型提升后
//补码为11111111 11111111 111111111 10000000
//因为打印的无符号整数,所以会把上面这个数当成无符号整数,所以其原码和补码相同,所以打印的为十进制数就为4294967168
printf("%u\n", a);
return 0;
}
同理:
c
#include<stdio.h>
int main()
{
char a = 128;
//128的原00000000 00000000 00000000 10000000
//反00000000 00000000 00000000 10000000
//补00000000 00000000 00000000 10000000
//存到a里面去需要截断:100000000-a
//存到a中的与上面例题中一样
//因为char有符号整型提升后为
//11111111 11111111 111111111 10000000
printf("%u\n", a);
return 0;
//打印结果仍为4294967168
- 求代码打印结果
c
#include<stdio.h>
int main()
{
char a[1000];
//-128~127
int i;
for (i = 0;i < 1000;i++)
{
a[i] = -1 - i;
}
//-1,-2,-3......-1000,但是我们要知道有符号字符型取值-128~127
//所以我们现在来算-129的
//原10000000 000000000 000000000 10000001
//反11111111 111111111 111111111 01111110
//补11111111 111111111 111111111 01111111
//01111111-a[]
//整型提升11111111 11111111 11111111 01111111
//所以存到字符型数组里的数为127
//-1,-2,-3.......-128,127,126......2,1,0
printf("%d", strlen(a));
//255
return 0;
}

- 求代码打印结果:
(1)
c
#include<stdio.h>
unsigned char i = 0;
//0~255
int main()
{
for (i = 0;i <= 255;i++)
{
printf("%u:hello world\n",i);
}
return 0;
}
//该程序进入死循环,且i不会超过255
(2)
c
#include<stdio.h>
#include<windows.h>
int main()
{
unsigned int i;
//>=0
//当i为负数时,会被当成无符号整型来看,转换过来就是一个非常大的数
for (i = 9;i >= 0;i--)
{
printf("%u\n",i);
Sleep(100);
}
return 0;
}
//程序陷入死循环

- 求代码打印结果
%x是以16进制的形式打印数据
c
#include<stdio.h>
int main()
{
int a[4] = { 1,2,3,4 };
int* ptr1 = ( int* )(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
//ptr1[-1]==*(ptr1-1)
return 0;
}

打印结果:
三.浮点数在内存中的存储
- 浮点数家族包括:float,double,long,double类型
- 浮点数表示的范围:float.h中定义
例如:十进制的浮点数:8.0 / 9.5
先转换为二进制的浮点数:1000.0 / 1001.1
写成标准形式就是:(-1)^ 0* 1* 2^3 /
(-1) ^ 0*1.0011 *2^3
- IEEE 754还规定:
对于32位的浮点数(float),最高1位的存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M;
对于64位的浮点数(double),最高1位的存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M
浮点数存的过程
IEEE 754对有效数字M和指数E,还有一些规定: 由于M>=1且M<2,所以M可写成1.xxxxxx的形式,xxxxxx表示小数部分。但其规定,计算机内部保存M时,默认这个数第一位总是1,因此可以被舍去,这是为了节省1位有效数字。
对于指数E(无符号整数): 若E为8位,则取值范围0 ~255;为11位,取值范围为0 ~2047。但科学计数法中的E可以出现负数,所以IEEE754规定:存入内存时E的真实值必须再加上一个中间数,对于8位数E,中间数就是127;对于11位E,中间数是1023。
例:2^10的E是10,保存为32位浮点数为10+127=137,即10001001
设计虽精妙,但有时候浮点数有可能是无法精确保存的,如十进制数字1.2转换为二进制,小数点后有很多位,在VS调试后可能会有些许误差
例:
浮点数取的过程
从内存中取E时有三种情况:
- E不全为0或不全为1(常规)
指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1 - E全为0
此时,浮点数的指数E等于1-127(或1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示正负0,以及接近于0的很小数字 - E全为1
这时,如果有效数字M全为0,表示正负无穷大(正负取决于符号位s)。
- 例题:
c
#include<stdio.h>
int main()
{
int n = 9;
//00000000 00000000 00000000 00001001
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);//9
printf("pFloat的值为:%f\n", *pFloat);//0.000000
//*pFloat会把9------>0 00000000 00000000000000000001001当成浮点数来解读
//E全为0
//标准形式(-1)^0*0.0000000000000000000000000001001*2^-126
//这个数无限接近于0,且float保留六位小数
*pFloat = 9.0;
//以浮点型内存存储解读一下
//二进制1001.0
//标准形式(-1)^0*1.001*2^3
//S=0
//E=3
//M=1.001
//01000001000100000000000000000000
//直接被认为补码转换一下
//1091567616
printf("n的值为:%d\n", n);
printf("pFloat的值为:%f\n", *pFloat);//9.0
打印结果:
本次内容到这里就结束了,谢谢观看!