深度剖析整数在内存中存储的问题

前言

我们都知道整数在内存的存储方式是以补码的形式存储的。但是具体存储的细节是如何的呢?
还有整数相关的一些源码反码补码,问题,一些数据为什么明明是负数,却输出时候是正数…等一系列的问题,都在这里会解决。

变量

🤪🤪🤪这里引出三个问题:

  • 什么是变量?
  • 变量用来做什么?
  • 为什么需要定义变量?

可能大家都没想过这个问题:

👀什么是变量?

答: 变量就是在内存中的某一个位置开辟的一段空间。具体在哪个位置,还是要看变量是局部变量,全局变量,静态变量,const变量等划分。


👀那为什么是在内存中的开辟空间,换句话说为什么在内存中定义变量,我们在硬盘中定义变量不行嘛?
答:因为我们程序在运行之前就已经加载到内存中了,而我们的变量又在程序中,就是说我们定义变量的时候就是在程序运行时候才开辟的,换句话说,定义变量只能在内存中开辟空间,不能再硬盘中定义变量。
做个类比就是:一种包含关系:内存包含程序;程序包含变量;所以变量也在内存中;


👀变量用来做什么?

变量具体来说就是用来存储数据的


👀 为什么需要定义变量?

其实很好理解,既然变量是用来存储数据的,那定义变量就是为了让数据保存起来供计算机使用,
👀那为什么要数据保存起来到变量中呢
在我们计算机中,cpu是处理数据是一行一行处理的,但是我们知道,有时候,我们有一些数据并不想让cpu一看到就处理掉了,我们希望程序能够执行到一定的代码后,我们才让cpu执行刚刚定义的数据,这个时候,就需要定义变量了,定义变量用来保存我们不想马上立即处理的数据。等待后续需要时候再处理。


换句话说:为什么需要变量?因为有些数据需要暂时被保存下来,等待后续的处理。


类型

接下来讲讲类型,我们知道定义变量时候是需要类型去参与定义变量的。但是有几个问题:

👀 为什么定义变量需要类型?

类型存在时为了让我们能够合理的使用内存空间的大小。定义变量不是目的,目的是能够使用变量的数据,但是数据的是有大有小之分的,我们是如何区分数据的大小的呢?就是通过类型去解析数据区分大小


👀为什么类型要分那么多种类?

因为计算机为了能够合理的去使用内存,在我们使用变量时候,根据自己的合理场景需求去使用特定合理的类型,这样会帮组我们合理的使用内存;总的来说:就是为了适用特定场景需求的变量而分类出来的类型种类,为的是能够合理的使用内存,利用最小的内存空间成本去解决特定场景需求的问题


👀C语言的内置类型分类由哪几种?
内置类型我们常见的那几种

char 字符类型 ,在大多数编译器默认为 signed char 即有符号类型
unsigned char 无符号类型
signed charshort int long 默认为 有符号的类型

short 短整型
unsigned short 
signed short 

int 整形
unsigned int
signed int

long 长整型
unsigned long 
signed long 

float 浮点类型
double 双精度浮点类型

类型有什么作用呢? 先做个铺垫,接下来会讲到

整数的原反补码的理解

我们知道整数在内存中是以二进制补码形式存储的,浮点数不是补码形式存储的,但是补码怎么来的呢?接下来看看几组相关的概念:

我们知道 整数分为:正整数和负整数,在计算机中,整数的二进制补码的最高位是一个符号位,符号位是用来区分整数是正数还是负数;在计算机规定:二进制补码最高位用0表示正数,用1表示负数

👀那如何得到整数二进制数的补码形式呢?

在我们计算机规定:正数的补码形式和原码反码相同;
负数的补码是 原码的符号位不变,其他位按位取反得到反码,反码在+1变为补码;


👀什么是原码反码补码?

原码就是整数转化为二进制数的样子,同时把符号位带上。

比如正数 2 的二进制数是 10,但这个在计算机中正数是 4 个字节,
所以我们应该用 32位 表示 2 的二进制,
所以 2 的原码:00000000 00000000 00000000 00000010;
其中最高位的 0 表示 符号位;

对于负数 -2 的二进制数,的原码 10000000 00000000 00000000 00000010;
其中最高位的 1 表示 符号位;

反码就是原码的符号位不变,剩余的其他位按位取反;
其他位为0,取反为1;其他位为1,取反为0。
由于正数的原码反码补码相同,所以反码就是为了负数来的。

正数的反码和原码相同
///
负数 -2 
原码:10000000 00000000 00000000 00000010;
反码:11111111 11111111 11111111 11111101

补码就是反码+1;
注意:即时是加1进位到了符号位,符号位也是需要加1的;总的来说,凡是涉及到加减法,只要是二进制数的位都需要相加。

正数的补码反码和原码相同
///
负数 -2 
原码:10000000 00000000 00000000 00000010;
反码:11111111 11111111 11111111 11111101;
补码:11111111 11111111 11111111 11111110

值得注意的是

我们虽然在计算机见到的是补码:但是我们人是需要阅读原码才可以得到我们需要的正确的数值的,所以一旦我们在计算机看到最高符号位为1的时候,要及时反应过来,这是补码,不是原码,你需要做的是,把补码转化为原码


👀如何把补码转化为原码?

你是用原码转化补码的,就是如何用反着来用补码转化为原码。
补码 - 1回到反码,反码符号位不变,按位取反得到原码

负数 -2 由补码转化为原码步骤:
补码:11111111 11111111 11111111 11111110
补码-1得到反码:
反码:11111111 11111111 11111111 11111101
反码按位符号位不变,按位取反得到原码:
原码:10000000 00000000 00000000 00000010

其实还有一种得到原码的方法:就是把补码认为是“原码”,在来一套 原码转化到补码的形式,然后得到的”补码“就是原码了。这个方法好处是:同一帮助你解决了原码转补码,补码转反码的问题;

负数 -2 由补码转化为原码步骤:
把-2的补码当作”原码“:
“原码”:
11111111 11111111 11111111 11111110
“原码”符号位不变,其他为按位取反反码:
反码:
11111111 11111111 11111111 11111101
反码+1得到得到补码:
“补码”:
10000000 00000000 00000000 00000010
这里的补码就是原码啦。

👀为什么计算机存放整型数据是以二进制补码的形式存储的?

因为计算机只认识二进制,所以用二进制存储;
用补码的原因是因为这样可以同一处理符号位和数值域;就比如上面处理二进制的原码反码转化方法二,就是同一的处理方法;
加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路,即cpu只设计加法器一个设备就可以完成运算了,不需要而外提供减法器的设备.


整形存储的本质

有了上面的基础,我们可以来了解一下整形存储的本质了。

先来看一段代码,判断这个代码是否可以编译通过?

unsigned int a = 10;
unsigned int b = -10;
请问这两语句代码能否编译通过?

答案是肯定可以的,并且我们大多数人都知道:unsigned int b = -10; 这条语句打印出来的值,是一个非常大的数值,并不是我们希望的 -10;因为这是无符号类型,是没有负数的,你用-10去存储结果肯定会出问题。但是为什么呢?

那接下来我们就分析分析 unsigned int b = -10;这条神秘的语句,揭示整形在计算机存储的本质问题。

首先我们清楚 定义变量时候,先开辟一段空间,然后整形数据在计算机存储的时候是先转化为对应的二进制补码,再存储到对应的空间中;
我们要有一个基本的认识,先有空间,再有数据内容
并且,整形的存储时候,空间并不关心内容是什么,这个数据内容可以直接存储到空间中;
换句话说,在数据保存在空间之前的时候,数据内容已经转化为对应的二进制补码了,并且直接存入到开辟好的对应空间中,不管你的空间是什么类型。
在这里插入图片描述
所以我们思考一下:变量b的是一个内存空间,但是我-10存入到变量b的时候,就已经转化为对应的补码了,这和变量b的类型无任何关系,即使变量b的类型是无符号类型,而数据是有符号类型的数据,也没关系;


👀那这么说变量b的类型unsigned int 什么时候起作用?更通俗的问:变量的类型有什么作用?

回答这个问题之前那,我先打个比喻:
我说我现在口袋里面由100块钱,请问我有多少钱?
哈哈哈,你口袋由100块钱,不就是由100块钱嘛?对,你说的对,但是也不对,我问你的时候,并没有说我的100块钱是 人民币还是美元,日元,韩元,欧元。。。。。这就说明,我什么的100块钱是不确定的,但是只要我给 100块钱加了一个人民币,那就说明我有100块钱人民币;

所以:引出的问题说明:数据是没有意义的,要带上类型才有意义。
那这么说,当我给你一个二进制序列数据时候,你是如何解释这个二进制的系列的?
是需要通过类型去解释这个二进制序列数据才有意义,比如是 int ,unsigned int ,double, float......

类型存在的意义作用
用来解释空间内部保存的二进制序列数据是以什么方式读取出来

回到刚刚的问题:
unsigned int b = -10;
printf("%d",d); //百分号d以有符号的形式解释数据;
printf("%u",d);//百分号u以无符号的类型解释数据

-10的补码:
11111111 11111111 11111111 11111010
printf("%d",d);的结果为 -10
printf("%u",d);的结果为:4294967286
由于解释的方式不同,所以得到的数据也是不同的:

👀变量存的过程:

数据不管三七二十一先把它转化为对应的补码形式,然后存入变量中,和变量是什么类型毫无干系。

👀变量取的过程:

先看变量的本身类型,然后再决定要不要看符号位,如果无符号的类型就不看符号位,有符号的类型就看符号位,再转化到对应原码去得到结果。


一些易错练习

♥♥♥输出什么?

#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 =-1; b = -1; c = 255;
  • 首先不管三七二十一,先把变量 a b c 的数据转化到对应的二进制补码,存入到变量中;
  • 然后,看变量的类型,是 char 把数据二进制补码解释为有符号,signed char 解释为无符,unsigned char 解释为无符号;
  • 由于他们都是 char 类型,所以在解释时候,会发生截断数据;
  • 然后,对于 %d 的解释是:以有符号的 int 类型解释数据。

大致解释过程:

	char a = -1;
原码:	10000000000000000000000000000001
反码:	11111111111111111111111111111110
补码:	11111111111111111111111111111111
看类型char截断:
	11111111   
	由于是%d是以:以有符号的 int 类型解释数据,
	而a是char所以要整形提升,char为有符号类型
	由于符号位是1,所以提升位置补上1:
提升后的补码:
	11111111111111111111111111111111

变为原码:10000000000000000000000000000001  

即结果为a = -1;

	signed char b = -1;
截断后的补码:	11111111   -b
%d打印结果 为 b = -1;
	unsigned char c = -1;
截断后的补码:	11111111   -c
由于unsigned char 为无符号类型,所以整形提升补上 0;
提升后的补码:
	00000000000000000000000011111111

由于%d是以有符号解释数据,符号位为0,所以是正数,结果为
c = 255;	

所以最终结果 a =-1; b = -1; c = 255;

♥♥♥输出什么?

#include <stdio.h>
int main()
{
  char a = 128;
  printf("%u\n",a);
  return 0;
}
//最终结果:a= 4294967168;

解释的文字如上一题:懂原理就可以,这里列出解释的大致过程:

 char a = 128;
 128的原码:
 00000000000000000000000010000000
由于是char类型所以会截断:
截断后a:
	10000000 
由于char是有符号的类型,所以整形提升,高位补上111111111111111111111111110000000
	由于%u是以无符号的int类型解释数据:
	所以得到a = 很大的数, a= 4294967168;

♥♥♥输出什么?

int main()
{
  char a = -128;
  printf("%u\n",a);
  return 0;
}
//最终结果:a = 4294967168

分析过程:

char a = -128;
 -128的原码:
 	10000000000000000000000010000000
 -128补码:	
	11111111111111111111111110000000
char类型截断:
	10000000
整形提升:由于是 char类型有符号,符号位为1,补上111111111111111111111111110000000
	
	%u 以无符号数的形式,打印整形
	所以最终结果:是正数,且a很大
	a = 4294967168

♥♥♥输出什么?

4.
int i= -20;
unsigned  int  j = 10;
printf("%d\n", i+j);

//按照补码的形式进行运算,最后格式化成为有符号整数
//最终结果为 a = -10;

解释过程:



	int i = -20;
	unsigned int j = 10;
	11111111111111111111111111101100 - -20的补码
	
	00000000000000000000000000001010 - 10的补码

	11111111111111111111111111110110 - 	i+j的补码:
	由于%d解释:所以结果为:
		10000000000000000000000000001010 -  i+j的原码
	所以a = -10;


还有很多题,朋友们可以自己碰到时候做做,会解释,懂原理最重要。

  • 73
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呋喃吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值