数据在内存中的存储

数据类型是我们在c语言里面最长用到的,每次声明变量时我们其实都使用到了数据类型,但是不同的数据类型在计算机内存中的存储是否相同?以什么样的形式存储?
常见的数据类型也是最基本的类型:

char //字符数据类型
short //短整型
int //整型
long //长整形
long long //更长的整型
float //单精度浮点型
double //双精度浮点型

这里注意一下char也算在整型里面,因为char类型在内存中是以ASCII码形式存储的,算作整型
那么类型的意义是什么?
1.使用这个类型所开辟的空间的大小;
2.如何看待内存空间的视角(指针)

类型的基本归类

在这里插入图片描述

整型在内存中的存储

数据在内存中是以二进制的形式存储的!

int a=10;
int b=-10;

要想知道二进制码是如何存储我们要先了解原码、补码、反码:
首先我们要搞清楚三种表示方法均有符号位数值位,符号位用0表示正,用1表示负
三种方法的转换方法如下
在这里插入图片描述

  1. 对于整型来说内存中存储的其实是补码。
  2. 计算机在计算时也是按照补码的形式进行运算

为什么?

因为计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(cpu只有加法器)此外,原码与补码相互转换运算过程是相同的,不需要额外的硬件电路。

字节序-大小端介绍

在这里插入图片描述
我们看上面这个程序,我们定义了一个变量a,对程序进行调试并监视a的内存,我们得到a的内存为 0a 00 00 00,我们知道变量在内存中是以二进制存储,但是我们这里是以十六进制显示,如图
我们按照以前的认知先写下变量a的二进制:
int a=10 -------- 0000 0000 0000 0000 0000 0000 0000 1010
转换为十六进制 0 0 0 0 0 0 0 a(每四个二进制对应一个十六进制)

我们得到的是00 00 00 0a与地址中存储的不太一样 这就涉及到大小端存储了。
在这里插入图片描述
其实大小端存储是一个很简单的问题,一个int是四个字节,内存中的最小单元是一个字节,要把这个4字节的int放到内存中,我们就要先开辟一个四个字节的空间,如图红方框所示,那么问题来了,四个字节(00 00 00 0a)要以什么顺序存入 这块空间。于是计算机以内存的高低为基准规定
小段存储:数据的低位保存在内存的高地址中,而数据的高位保存在在内存的低地址中
大端存储 :数据的高位保存在内存的高地址中,而数据的低位保存在在内存的低地址中
00 00 00 0a中0a是数据的低位(最右边的位,高位是最左边的位)存储到了内存的低位,于是我们判断这是小段存储模式。
大小端实际上是对字节序的排列方法

如何判断字节序

#include<stdio.h>
int main()
{
	int a = 1;
	char* p = (char*)&a; //强制类型转换
	if (*p == 1)
		printf("小端存储");
	if (*p == 0)
		printf("大端存储");
	return 0;
}

这里利用指针类型char一次只能访问一个字节的特性完成判断。

关于signed和unsigned整型的表达式运算

  1. char类型到底使signed char还是unsigned char在c语言中并没有规定,取决于编译器。
  2. int 和short 规定为signed int。

有符号与无符号整数的整型提升问题

#include<stdio.h>
int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("a=%d,b=%d,c=%d", a, b, c);
	return 0;
}

我们直接写出二进制序列:
a=b=c: 11111111
但在执行到 %d 的时候要发生整型提升,由转型提升的规则可知:有符号位的补符号位,无符号为的补零,于是二进制序列就发生了变化

c :00000000000000000000000011111111

a=b:11111111111111111111111111111111

又因为以%d的形式打印,所以结果是 -1 -1 255

注意:%d %u都是打印整型类型,顾打印时要发生整型提升!

#include<stdio.h>
int main()
{
	char a = 128;
	char b = -128;
	pritnf("%u,%u",a, b);
	return 0;

}

我们就得到二进制位:

a=b: 10000000;

%u打印时发生整型提升:

11111111111111111111111110000000

故结果是:4294967168,4294967168

其实我们在这里发现了一个规律,%d和%u是最终决定对二进制序列是按有符号还是无符号读取,也就是决定了读取方式,和打印的变量是signed或unsigned无关。这里signed char和unsigned char符号位只在转型提升中有用。

下面我们来探究一下signed char数值随着二进制增大的规律

下图二进制从零逐渐增加,char的值的变化:在这里插入图片描述
signed char有一位是符号位实际上决定大小只有七位,但是signed char和unsigned char的区间长度都是一样的都为28 ,需要注意的是上一题char 128和char -128实际上是同一个数,所以整个char是一个周期,大小始终是在-128到127之间,如正弦函数一样。

#include<stdio.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;

	}
	printf("%d", strlen(a));
	return 0;

}

这里a[i]从-1开始减小,按照上图的循环减小到-128时下一个数时127 126…1 0 -1 …
但要注意的是 0对应的ASCII码为’\0’顾strlen到0的时候就会停止。

signed与unsigned类型的大小比较

无符号int 类型 与 有符号int类型:
#include<stdio.h>
int main()
{
	int a = -1;
	printf("%d", a > sizeof(a));
	return 0;
}

这个程序的结果:如果a大于sizeof(a)表达式为真返回并打印1,反之返回并打印0;
已知a=-1 , sizeof(a)=4所以a<sizeof(a), 所以答案为0。其实答案并不是0!
在这里插入图片描述
实际上答案为1,这代表着a>sizeof(a),这是为什么呢?
这里sizeof其实返回的是unsigned int的值,而a是一个int值,**int和unsigned int比较时有符号数要转换成无符号数!**所以a的二进制码为32个1,也就是232 -1肯定比4大,所以返回的是1

无符号int 和 非int无符号 类型

#include<stdio.h>
int main()
{
	char a = -1;
	unsigned int b = 1;
	printf("%d", a > b);
	return 0;
}

这里a>b时必然会发生整型提升!a从11111111提升为1111111111111111111111111111(32个1),又因为b为unsigned int类型所以a会被转换成232 -1,所以结果为1;

有符号int类型 和 非int无符号类型
#include<stdio.h>
int main()
{
	unsigned char c = -1;
	int d = -1;
	printf("%d\n", c > d);
	return 0;

}

这里char先发生整型提升,转换成signed int类型进行比较!答案为1

不含int类型

处理方法时转型提升转换成有符号int类型进行比较!

规律

一旦出现unsigned int类型一律转换成无符号数进行比较,如果没由出现unsigned int一律转换成有符号int进行比较!

浮点型在内存中的存储

常见的浮点数:
float 、double 、long double
浮点数存储的例子:

#include<stdio.h>
int main()
{
	int a = 9;
	float* p = (float*)&a;
	printf("%d\n", a);      //以整型的视角存储
	printf("%f\n", *p);  //以浮点数的视角读取

	*p = 9.0;             //以浮点型的视角存储
	printf("%d\n", a);   //以整型的视角读取
	printf("%f\n", *p);
	return 0;
}

在这里插入图片描述

这是我们输出的结果,已知float与int一样也是四个字节,但为什么输出的结果不一样?
实际上储存和翻译在内存中的二进制码,既可以解释为整型,也可以解释成浮点型,这取决于他们被使用的方式。如果使用的是整型的算数指令就会被解释为整数。如果使用的是浮点型指令,他就是个浮点数。
总结为一句话:浮点数和整数在内存中存储,读取方式都不同!

浮点数在计算机内部的表示形式:

根据国际标准IEEE754,任意一个二进制浮点数v可以表示成下面的形式:

  • (-1)S *M *xE
  • (-1)S 表示符号位,当S=0,V为正数;当S=1,V为负数
  • M表示有效数字,大于等于1,小于2。
  • 2E 表示指数位

例如:float 5.0 二进制位: 101.0=1.010*22
S=0 M=1.010 E=2;
对于32位浮点数,最高位的1位是符号位是,接着的8位是指数E,剩下的23位是M
32位操作系统

IEEE754对E和M还有一些要求:

  • 在上面M始终是1.xxxxxxxxxxxx的形式,但是保存到23位中可以将“.”前面的1省略,例如1.01就保存01后面补0。
    这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M的只有23位,如果舍去1,就可以保存24位有效数字。
  • 对于E:因为E是一个无符号的数这意味着,如果E为8位,它的取值范围0-255;如果E为11位,它的取值范围为0-2047.但是我们知道,科学计数法中E可能是负数的,所以IEEE754规定E先加上一个中间数(32位为127,64位为1023)。

然后E内存中取出还可以再分成三种情况

  • E补全为1或全为0,这时就逆序按照上面的方法,即指数E减去127,得到真实值,再将有效数字M前加上第一位1;
  • E全为0这时如果我们还按照上面的做法,会发现E减去127后是一个非常小的数,有效数字M不再加上第一位1,而是还原位0.xxxxxxxxxxxx的小数。
  • E全为1这时,如果有效数字M全为0,表示正/负无穷大。
#include<stdio.h>
int main()
{
	int a = 9;
	float* p = (float*)&a;
	printf("%d\n", a);      //以整型的视角存储
	printf("%f\n", *p);  //以浮点数的视角读取

	*p = 9.0;             //以浮点型的视角存储
	printf("%d\n", a);   //以整型的视角读取
	printf("%f\n", *p);
	return 0;

再看上面的代码块:
第一部分:按int的视角存储
内存二进制位: 00000000000000000000000000001001
按float的视角去看这个二进制:
0 00000000 00000000000000000001001
S=0 E=-127 M=00000000000000000001001
因为E全为0,顾打印结果位0.0
第二部分按float视角存储
9.0 -> 1001.0 -> 1.001*23
S=0 M=1.001 E=3+127=130
0 10000010 00100000000000000000000
以int类型的视角去读取:1091567616

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值