目录
前言
之前我们粗略地总结过数据的类型,现在我们来总结一下数据的存储
一、整型在内存中的存储
1.计算机中的整数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位正整数的原、反、补码都相同。负整数的三种表示方法各不相同,原码:直接将二进制按照正负数的形式翻译成二进制就可以。 反码:将原码的符号位不变,其他位依次按位取反就可以得到了。 补码:反码+1就得到补码,如图:是-1的原反补码:
2.对于整型来说:数据存放内存中其实存放的是补码:
原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
变量a的创建是要在内存中开辟空间的,空间的大小是根据不同的类型而决定的。此处a已经被分配了4个字节的空间。我们可以看到a存储的是补码,注意:这里是16进制,1个16进制数=4个2进制数,但是为什么是10000000?这个顺序感觉怪怪的。其实这涉及到大小端存储的问题。
3.大小端介绍
(1)大端(存储)模式:指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。(大端字节序存储)
小端(存储)模式:指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。(小端字节序存储)
如图所示:(VS的 X86 结构是小端模式)
(2)存在大小端的原因:
在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的 short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式
(3)我们来看一道题:
#include <stdio.h>
int check_sys()
{
int i = 1;
return (*(char *)&i);
}
int main()
{
int ret = check_sys();
if(ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
解析如下:
我们都知道指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。(char *)&i所以我们将i的地址(类型为int*)强制类型转换为(char*),再解引用,访问的就是i的第一个字节,如果访问到是1,那么就是小端字节序存储;如果访问到是0,那么就是大端字节序存储
4.接下来,我们来看以下一些练习:
(1)
#include <stdio.h>
int main()
{
char a= -1;//-1的补码存到a里面去,然后发生整型截断
signed char b=-1;//-1的补码存到b里面去,然后发生整型截断
unsigned char c=-1;//-1的补码存到c里面去,然后发生整型截断
printf("%d %d %d",a,b,c);//-1 -1 255
return 0;
}
解析如下左图:
(2)
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);//4294967168
//以无符号整型的形式打印,此时就不存在原反补码的概念了
return 0;
}
解析如上右图:
(3)
#include <stdio.h>
int main()
{
char a = 128;//char存的有效值:-128~127
printf("%u\n",a);//4294967168
return 0;
}
解析如下左图:
(4)
#include <stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);//10
//按照补码的形式进行运算,最后格式化成为有符号整数
return 0;
}
解析如上右图:
(5)
#include <stdio.h>
int main()
{
unsigned int i;//无符号数,所有位都是有效位
//-1补码:11111111111111111111111111111111即十进制的4294967295
for(i = 9; i >= 0; i--)
{
printf("%u\n",i);//9 8 7 6 5 4 3 2 1 0 4294967295 4294967294.......死循环
}
return 0;
}
(6)
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000];
int i;
for(i = 0; i < 1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));//255
return 0;
}
解析如下:
(7)
#include <stdio.h>
int main()
{
unsigned char;
for(i = 0;i <= 255;i++)
{
printf("hello world\n");//此处会打印无限个hello world
}
return 0;
}
解析如下:
二、浮点型在内存中的存储
1.浮点型存储规则:
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式: (-1)^S * M * 2^E
IEEE 754规定: 对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M;对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。,如图所示:
IEEE 754对有效数字M和指数E,还有一些特别规定:

然后,指数E从内存中取出还可以再分成三种情况:
int main()
{
int n = 9;
//9在内存中的存储:00000000000000000000000000001001(原反补码相同)
float *pFloat = (float *)&n;
//pFloat认为9是浮点数 所以9在内存中的存储应该是以浮点型的方式存储0 00000000 00000000000000000001001
//E全为0,∴E=1-127=-126
//M=0.00000000000000000001001
//0.00000000000000000001001*2^-126这个数基本趋于0,且%f只能打印小数点的后6位,因此0.000000
printf("%d\n",n);//9
printf("%f\n",*pFloat);//0.000000
*pFloat = 9.0;
//9.0的二进制:1001.0
//科学计数法:(-1)^0*1.001*2^3
//S=0
//E=3 +127存储
//M=1.001
//0 10000010 00100000000000000000000
//%d认为此时内存中存的是有符号整数,符号位为0,为正数,原反补相同,01000001000100000000000000000000转化为十进制:1091567616
printf("%d\n",n);//1091567616
printf("%f\n",*pFloat);//9.000000
return 0;
}