数据在内存中的存储_C语言

本文深入探讨了C语言中整型在内存中的存储方式,包括整形家族、原码、反码和补码的概念,以及大小端字节序。详细解释了正负数的原反补码原理,并通过实例展示了整型运算过程。同时,文章还介绍了浮点型的存储形式,以帮助读者理解数据在内存中的表示和计算。
摘要由CSDN通过智能技术生成

1 整形在内存中的存储

1.1 了解整形家族

首先,我们先来认识一下整形家族有哪些成员

1.1.1 整形家族有哪些成员?

整形家族的成员有以下

char  1字节(是的,char也属于整形家族)
short 2字节
int   4字节
long  4/8字节

而每个整形都有对应的 有符号类型和无符号类型

char  没有标准是否是有符号还是无符号
signed char 有符号位
unsigned char 无符号位

short 默认有符号位
signed short 有符号位
unsigned short 无符号位

int 默认有符号位
signed int 有符号位
unsigned int 无符号位

long 默认有符号位
signed long 有符号位
unsigned long 无符号位

1.1.2 有符号位和无符号位的区别

只有整形才会区分有符号位和无符号位。有符号位和无符号位的区别在于二进制的最高位是符号位,还是数据位。简单来说,有符号位,就是有正数和负数。无符号位,就代表没有负数,只有正数。而有符号位的最高位如果是1,那么这个数就为负数。如果为0,那么这个数就为正数。如果是无符号位,那么它的最高位无论是0还是1,它都是正数。

比如 有符号数-1的 原码是 10000000 00000000 00000000 00000001
但如果这个数是个无符号数,那么最高位的符号位就会变成数据位,也就是一个非常大的数字。


1.2 整形的原码、反码和补码

1.2.1 原码,反码,和补码之间的关系

无论是原码,反码,还是补码,都是以二进制的形式存在的。原码是一个值最直观的体现,也是人脑最容易理解和计算的表示方式。我们可以很轻松的通过原码知道一个数所对应的值。比如有一个数10,把它转换成2进制就是1010。再在它前面补0,所对应的就是它的原码。一个正数的原码,反码,补码是相同的。但是在负数中,反码是原码按位取反后的结果,补码是反码+1后的结果。

1.2.2 正数的原码,反码,补码

前面我们讲到过,每个整形在内存中所占的 字节大小。而每个字节对应 8 个比特位,存放于内存中。 而一个数的原码,反码,补码,就是由一个个比特位组成。 原码 就是一个值 以二进制展示,有符号位的负数则最高位为1,正数最高位为0 。 比如10的二进制是 1010,而整形是4个字节,有32个比特位。而10 只能用掉4个比特位。剩下的28位怎么办? 那就往前补0!,负数最高位为1,正数最高为为0。

10  是整形,4个字节大小
10  -> 1010000000000 00000000 00000000 00001010 这就是10的原码
 如果是-10呢? 那只需要把最高位变成1就行了,因为这是个有符号整数
 -10
 10000000 00000000 00000000 00001010  这就是-10的原码了

现在我们知道了 10的原码是 00000000 00000000 00000000 00001010,前面说到过正数的原码,反码,补码相同。那么我们怎么验证呢?很简单,因为整形在内存中都是以补码形式存在的,我们只要找到它的补码,然后拿来与原码做对比。


int main()
{
	int a = 10;
	
	return 0;
}

我们去内存,看一下10的补码(因为整形都是以补码形式存放于内存中的)
在这里插入图片描述
我们通过取地址,找到了a所对应的值,是 00 00 00 0a 因为小端存储,所以从后往前取,后面会说 ,接下来我们把 00 00 00 0a 转换成二进制,因为内存是以十六进制展示的,但是实际上也是二进制。

00 00 00 0a  一个16进制数等于 4个二进制数
00000000 00000000 00000000 00001010
这就是十六进制转换二进制后在内存中的补码。

所以我们可以得到结论 : 正数的原码,反码,补码相同

1.2.3 负数的原码,反码,补码

负数的原码,反码,补码,是不同的,为什么不同呢?因为计算机里只有加法器,没有减法器。乘法和除法都是通过加法模拟来的。因此负数的原码,反码,补码,不能相同,否则会出现运算错误。例如:

在这里插入图片描述

如果 - 1的原码和补码相同,那么它们相加后的结果就是
在这里插入图片描述
我们可以发现, 相加后的结果最高位是1,说明它是个负数,最后的结果是 -2。 1-1怎么可能=-2?所以,我们可以确定。 负数在内存中的补码 和原码肯定是不同的。
负数的反码 : 原码除了最高位,其他位按位取反
负数的补码 : 反码+1
因此我们可以按照这个规律,求出-1的反码和补码
在这里插入图片描述
这样,我们就得到了 -1的补码,而后我们在加上 1 的补码(原反补相同)
在这里插入图片描述
我们会发现,相加后得到的数字(计算机只有加法器,没有减法器,所以运算时只能是相加) 是 1 00000000 00000000 00000000 000000000, 但是一个整形是4个字节,只能存放32个比特位,而相加后 有33位比特位了。那么最高位就会被截断,只保留后面的 32 。也就是 32个 0 ,32个0 对应的数就是0。
在这里插入图片描述
我们再用程序演示一下,到底是不是这样。

int main()
{
	int a = 1;
	int b = -1;
	int c = a + b;
	return 0;
}

我定义了三个变量,分别存储1,-1 和它们相加后的值。
在这里插入图片描述
首先,我们找到了a的地址。
a在内存中的 16进制表示为
00 00 00 01
转换成二进制为
00000000 000000000 000000000 00000001
在这里插入图片描述
随后,我们在内存中找到了b的地址
b在内存中的 16进制表示为
ff ff ff ff
转换二进制为就是
11111111 11111111 11111111 11111111
到这里,我们应该可以清楚的发现,负数的补码和原码是不同的。因为-1的原码是1000… 1。
那我们再把a和b相加后的值存入c,再观察c的地址。
在这里插入图片描述
我们发现c在内存中的值是 00 00 00 00 ,也就印证了我们刚刚的例子。因此我们可以总结出以下结论。

1.正数的原码,反码,补码,相同
2.负数的原码最高位是1,反码是最高位不变,其他位按位取反,补码=反码+1
3.内存中存放的是补码,因此进行运算的也是补码
4.如果相加后的值超过了所对应类型的字节大小时(比如整形四个字节,运算后的值大于32位比特位),会从最高位开始发生截断

1.3 整形在内存中的运算以及整形提升

我们上面有讲过 1 + (-1)的例子,也总结出一些运算规则,那么我们在这里继续深入了解。

1.3.1 整形在内存中的运算

我们再来举1个例子。


int main()
{
	int a = 10;
	int b = 20;
	int c = a - b;
	return 0;
}

我们可以发现a 和 b 都是正数
所以 a 和 b 的原反补都是
在这里插入图片描述
那么c = a - b 是如何运算的呢?
我们前面提到过,计算机只有加法器,没有减法器,所以 a - b 会被计算机转换成 a + (-b)
所以 10 - 20 会被转换成 10 + (-20)
那我们就要算出 -20 在内存中的补码
在这里插入图片描述

再拿 10的补码与 -20的补码相加
在这里插入图片描述
我们把这串值转换为 16进制
11111111 11111111 1111111 11110110
转换为16进制
ff ff ff f6
然后去内存看看c的值
在这里插入图片描述
我们发现对上了,那我们如何直观的知道 它们的结果呢? 我们可以再把补码转换成原码
已知 补码 = 反码 +1, 原码按位取反得到反码 ,那么我们可以
补码 -1 得到反码,反码按位取反,得到原码
在这里插入图片描述
转换后 我们得到了 -10的原码, 数据位的结果是 10 ,符号为1表示负数,所以就是-10了。

1.3.2 整型提升

但是,当我们运算的类型不是同类型的整形家族成员。这个时候,就会进行整形提升,会把低字节的数,转换成和高字节一样的字节数进行运算。比如

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

我们都知道 char 只占1个字节, int 占 4个字节,那么把 a 赋值给 c的时候,c是低字节的数,int是高字节的数。所以我们会对c进行整形提升,会把c 短暂提升到 4个字节,32个比特位。随后发生截断。
在这里插入图片描述
在这里插入图片描述

那我们一起来打印一下结果,看看c的 补码是 10000000 ,如果是有符号的char类型,这个会被直接解析为 -128。
我们来看看结果是不是 -128
在这里插入图片描述
这个例子可能不太好,那我们再来一个

int main()
{
	int a = -500;
	char c = a;
	printf("%d",c);
	return 0;
}

在这里插入图片描述

那我们来看看运行结果吧
在这里插入图片描述

上面的举例都是 有符号整形,那我们来举个无符号整形的例子

int main()
{
	char b = 257;
	unsigned char c = 257;
	printf("%d,%d\n",b,c);
	printf("%u,%u", b,c);

	return 0;
}

问结果分别输出什么? 我们先来分析一下,因为 129是个整形,所以赋值给c时,c会进行整形提升,后面再发生截断。

在这里插入图片描述当我们用%d 打印时,也会进行整形提升后再打印,因为%d是打印一个整形,而char类型不够一个整形,所以依然会对char类型进行整型提升。这时整形提升,如果是有符号数,那么前面 补 符号位,如果是无符号位,默认补 0

在这里插入图片描述
而 b是以补码形式存储的,我们将它抓换成原码
在这里插入图片描述
1111111 对应的十进制数是 127 ,最高位为1 是负数,所以 b打印的结果是 -127

而 c 整形提升后的最高位是 0 ,10000001的十进制数是 129
我们来看看运行结果
在这里插入图片描述
那我们再来看看第二行,%u是打印无符号整形,所以也会进行整形提升,整形提升后是
在这里插入图片描述
因为是打印无符号整形,所以b的最高位是数据位,不是符号位。无符号整形只有正数没有负数
我们用计算器来看看 b的值
在这里插入图片描述
是一串很大的数字,而c 以无符号整形打印,和刚刚一样,也是 129。让我们来看看程序运行结果

在这里插入图片描述
和我们推理的一样

1.4 大小端字节序存储方式

大端存储模式 : 数据的低位保存在内存的高地址,数据的高位保存在内存的低地址
小端存储模式 : 数据的低位保存在内存的低地址,数据的高位保存在内存的高地址

1.4.1 如何判断你是大端存储还是小端存储?

很简单,我们只需要定义一个变量,然后进内存看它的存储顺序就好了。
我定义了一个16进制的变量

int main()
{
	int i = 0x11223344;
	return 0;
}

然后我们去内存找这个变量
在这里插入图片描述
因为 int 占四个字节,所以我们找到了它对应的四个字节地址,并且发现地址它的低地址处存放的是 44 , 而 0x11223344,数据的高位是 11 ,低位是44 。 所以我这台机器是
数据的低位存放于内存的低地址处,高位存放于高地址处,是小端存储模式。

1.4.2 写一个函数判断当前机器是大端存储还是小端存储

首先,我们可以定义一个整形,赋值为1,随后它 的地址强制转换成char *,让它 & 1
1 在内存中的二进制是
00000000 00000000 00000000 00000001
写成16进制就是
00 00 00 01
这时 我们再把定义的变量的地址强制转换成 char * 类型 并且&上1
强制转换成char * 类型后,那么就只能访问第一个字节
在这里插入图片描述
如果此时的第一个字节为 0 ,那么就说明是大端存储方式, 1的低位存放在高地址处
如果是 1,那么就说明是小端存储方式, 低位存放于低地址处

int check_sys()
{
	int i = 1; 
	//访问 i的第一个字节并且返回,返回值1 为小端存储,0为大端存储
	return (*(char*)&i); 
}

2 浮点型在内存中的存储

2.1 浮点数的存储形式

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

举个例子
2.5 这个数字
写成二进制是 10.1
小数部分为 2的-1次方+2的-2次方… 0.5为 2的-1次方,所以为1
10.1小数点向前挪一位,相当于
1.01 * 2^1
M = 1.01
E = 2
而它们在内存中存储的方式为
在这里插入图片描述

在这里插入图片描述
前面说过 1<M<2 ,所以计算机在内部保存M时,M被自动默认为1,因此M可以被舍去,只保留后面的 .xxxxx。 比如保存 1.0110 的时候,只会把 0110保存进去,等到读取时,才会把这个1加上。这样做目的是 可以多出 1个bit的位空间。

而指数E的 在存储时, 如果是 float类型,E会 加上1个127,如果是double类型,则加上1个1023,目的是为了不让E变成负数。 比如 0.101, 小数点会向后移动1位,这样E就变成了-1。这样子放进内存是非常麻烦的,所以会加上一个127 或者 1023。

还有一点, 当E在内存中为全0时, 那么M 会自动被默认为0。

那接下来我们就来分析一段代码


int main()
{
	int n = 15;
	float* p = (float*)&n;
	printf("%d\n",n);
	printf("%f\n",*p);
	*p = 15.0;
	printf("%d\n", n);
	printf("%f\n", *p);
	return 0;
}

首先,我们的 n 是 15,在内存中的补码为
在这里插入图片描述
此时用 %d打印,我们会直接打印15。
但是 以浮点型打印时 , 它会把00000000 00000000 00000000 00001111 当成浮点数打印

我们知道 公式是 (-1)^S * M * 2 ^ E
在这里插入图片描述

s = 0 ,说明为正数 -1^0 = 1
M = 0.000… 1111
因为E在存储时加上了 1个 127!,所以在读取时,E必须再减去这个 127,所以E = 0-127 = -127
最后的结果应该为
1 * 0.000 … 1111 * 2^-127
这是一个特别特别小几乎为0的数字。所以打印会给我们显示 0.000000
我们来看看打印结果。
在这里插入图片描述
结果和我们预料的一样。
接着程序又进行了下一步操作,它把 指向 n的指针解引用并且赋值了一个 15.0。
那么在内存中,就会 把 15.0 以浮点型数的形式存入 n的地址中
我们先来计算一下 15.0 在内存中存储的样子
在这里插入图片描述

在这里插入图片描述

最后我们得到
01000001 01110000 00000000 00000000 换成16进制为
41 70 00 00
我们去内存看看,是否对应这个值
在这里插入图片描述
内存中这会存储的是
01000001 01110000 00000000 00000000 ,我们把它按%f打印 会打印出 15.00000…
如果按%d打印,那么会默认认为存放的是个整形,所以就会把它按整形打印。因为最高位是0,所以这个数是正数,我们拿计算器看看这个数是多少
在这里插入图片描述
然后我们打印看看结果
在这里插入图片描述
结果和我们算出来的一样。

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值