c语言 数据在内存中是怎么存储的?

相信初始编程的你心中一定有这样的疑惑,在内存中,整数是怎么存储的?小数又是怎么存储的?负数与正数又是怎么区分的?别急别急,这篇文章带你get所有数据在内存中的存储知识点。

1. 整数在内存中的存储

1.1 原码,反码,补码

对于整数,计算机有三种表示方式:原码,反码,补码。每种方式都是用二进制表示的,并且每种方式都是由两个部分组成:符号位与数值位。

无论哪种方式,符号位都是二进制序列中,从左向右数的第一位。符号位用1表示负数,0表示正数。正数的数值位都是相同的,但负数的每种方式的数值位都不相同,也就是说正数的原反补相同,负数的原反补不同。

原码
把一个数的十进制直接转换为二进制序列就是原码
反码
符号位不变,数值位按位取反得到反码
补码
反码加1得到补码

讲完理论,我们来看一个例子:10在内存中是怎样存储的

int main()
{
	int num = 10;
	//00000000 00000000 00000000 00001010 - 10的原码
	return 0;
}

由于10是个正数,正数的原反补相同,所以10的补码就是原码。

一个负数在内存中又是如何存储的呢?

int main()
{
	int num = -5;
	//10000000 00000000 00000000 00000101 - -5的原码
	//11111111 11111111 11111111 11111010 - -5的反码
	//11111111 11111111 11111111 11111011 - -5的补码
	return 0;
}

由于-5是负数,需要把-5的原码进行转化得到补码,只有补码才是负数在内存中的存储方式。

在这里插入图片描述

我们还能通过num的地址来观察num中存储的数据(一个整形有4个字节,也就是32个比特位,但在观察时,直接表示32个比特位过于复杂,而且也不好进行换算。所以编译器用十六进制来表示数据在内存中的存储,只是表现的形式!4个比特位表示一个十六进制数,这里的10表示为二进制是1010,十六进制为a)。但是我们发现10的存储顺序有点奇怪,不是00 00 00 0a,而是0a 00 00 00。这又是为什么?因为你的机器是小端存储模式。

1.2 大小端字节序

大端存储模式:数据的高字节位存储到低地址中,低字节位存储到低地址中。

小端存储模式:数据的低字节位存储到低地址中,高字节位存储到高地址中。

刚才观察的内存窗口中,地址从上往下是逐渐递增的,10的二进制是00 00 00 0a,把低地址的0a先储存到低地址中,所以0a在左边,接着是高字节序的00,以此类推。

再来个例子,直观感受小端存储模式。

我们直接把一个十六进制数11223344放进num中,按照小端存储模式,低字节序的44会先放到地址中,接着是33,22,11。编写程序,验证我们的说法
在这里插入图片描述

1.3 几个小练习

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;
}

signed char 可以简写成char所以a与b的类型一样,-1的补码为11111111 11111111 11111111 11111111,储存在字符类型中要发生截断,截断后是11111111,打印时把补码转换成原码:10000001,结果就是-1。而c中的数据也是11111111,但是把这串二进制序列当作无符号数来解析,就不存在原反补的转换问题,所以把11111111转换成十进制就是255。

在这里插入图片描述


int main()
{
	char a = -128;
	printf("%u", a);
	return 0;
}

-128的补码表示为11111111 11111111 11111111 10000000,储存到字符类型中要发生截断,截断后a中储存的值为10000000,再以无符号整形的形式打印,因为a是有符号的char,所以整形提升后为:11111111 11111111 11111111 10000000,把这串二进制序列当作无符号整形解析,
在这里插入图片描述
得到4294967168,编写程序验证
在这里插入图片描述


int main()
{
	char a = 128;
	printf("%u", a);
	return 0;
}

128在截断后于-128的结果一样,都是10000000,之后的细节就于刚刚的那题一样,这里就不再赘述了


在这里插入图片描述



int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d", i + j);
	return 0;
}

i作为一个负数,是以补码的形式存储在内存中的,-20的补码为11111111 11111111 11111111 11101100,j的二进制序列是00000000 00000000 00000000 00001010,i + j的结果为11111111 11111111 11111111 11110110,转换为原码再转换成十进制是10000000 00000000 00000000 00001001,结果是-10。

在这里插入图片描述


int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}

输出先从9打印到0,接着i成为-1,-1的补码为11111111 11111111 11111111 11111111,但是这是一个无符号数,不存在原反补转换的问题,直接转换成十进制是一个42亿多的数字,始终满足i >= 0。

在这里插入图片描述
如果计算机会一直算,这个42亿多的数字会减到9,8,…一直到0,然后又是这个42亿多的数字,程序陷入死循环…
在这里插入图片描述



int main()
{
	char a[1000];
	int i = 0;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d",strlen(a));
	return 0;
}

用strlen求字符串长度时,需要注意的一点是strlen遇到0停止,所以这一题要找哪个元素的值为0。我们知道char能存储的数据大小范围是-128 ~ 127,i从-1到-128,数组长度为128,-128-1 = 127,再从127到1,数组长度为127,之后就是0,所以数组长度为255。

在这里插入图片描述



int main()
{
	unsigned char i = 0;

	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}

无符号字符类型的数值范围是0 ~ 255,当i = 255时再+1得到的数是0,因此循环无限进行。
在这里插入图片描述
如果对这两题还有不清楚的地方,建议看看这一篇博客关于数据类型存储范围的详解

2. 浮点数在内存中的存储

2.1 存储规则

根据国际标准IEEE(电气与电子工程协会)754规定,一个浮点数v可以表示成

v = (-1) ^ S * M * 2 ^ E

  • 其中(-1) ^ S 表示符号位i,S = 0时,v为正数,S = 1时,v为负数
  • M 表示有效数字,大于等于1,小于2
  • 2 ^ E 表示指数位

单精度浮点数(32bit)中,最高位表示符号位,接着是8位指数位,最后是23位数字位。
在这里插入图片描述

双精度浮点数(64bit)中,最高位表示符号位,接着是11位指数位,最后是52位数字位。
在这里插入图片描述
对于有效数字M来说,由于1≤M<2,所以M又能写成1.xxxxxx的形式,M的第一位默认是1,这样首位的1就能舍去。所以IEEE 754规定,在存储M时只存储xxxxxx的内容,等到读取时再将1加上。

但是对于指数E来说情况就复杂了许多,E表示一个无符号整数,但是科学计数法中E可以是一个负数的。所以IEEE 754规定,E不全为0或不全为1,单精度浮点数存储E时,要加上一个中间数127,双精度浮点数存储E时,要加上一个中间数1023。

E全为0时,E的真实值为(1 - 127)或者(1 - 1023),并且M不再加上1,表示的数无限接近于0。

E全为1时,表示的数为无穷大或者无穷小。

说了这么多理论,举个例子:单精度浮点数5.5是怎么在内存中存储的?

把5.5转换成二进制是:101.1,可以表示为(-1) ^ 0 * 1.011 * 2 ^ 2。S = 0,M = 011,E = 2 + 127 = 129。

129的二进制是

在这里插入图片描述
这样我们得到5.5的二进制序列是0 10000001 01100000000000000000000

在这里插入图片描述
调试程序,验证浮点数在内存中存储的数字

在这里插入图片描述
通过验证可以看出我刚刚的推断是对滴

2.2 再来一个例子

int main()
{
	int a = 7;
	float* p = (float*)&a;
	printf("%d\n", a);  //表达式1
	printf("%f\n", *p); //表达式2
	*p = 7.0;
	printf("%f\n", *p); //表达式3
	printf("%d\n", a);  //表达式4
	return 0;
}

四个表达式打印的结果分别是什么?通过刚刚存储规则的讲解相信这个例子就不难了。

表达式1很简单,初始化一个整形为7,再以整形的格式打印,结果当然是7了。

在这里插入图片描述
但是表达式2呢?先写出7的补码:00000000 00000000 00000000 00000111,把p从整形指针强制转换成浮点型指针,解引用p时,p就会以浮点数解析它指向的内容。

根据刚刚的讲解,把7的补码以浮点数解析,我们得到S = 0,E = 0,M = 00000000000000000000111,而E全为0,所以表示的数无限接近于0。

在这里插入图片描述
由于p是一个浮点型指针,解引用p并改变p所指向内存空间的值时,p总是把数据当作浮点数来处理。7的二进制是111,可以表示成v = (-1) ^ 0 * 1.11 * 2 ^ 2,所以7用浮点数表示是0 10000001 11100000000000000000000,这个二进制表示的十进制是
在这里插入图片描述
在这里插入图片描述

与打印结果相同。


最后,对于数据在内存中的存储这一部分的笔记到此结束,希望这篇文章能对你有帮助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值