整数在内存中原来是这样存储的,看完表示头好痒,感觉要长脑子了!

简介: 整数在内存中原来是这样存储的,看完表示头好痒,感觉要长脑子了!

本篇文章来介绍一下整形在内存中的存储,内容丰富,干货慢慢。

1.整形家族

在开始之前,我们先来简单回顾一下整形家族:

char
        unsigned char
        signed char
short
        unsigned short
        signed short
int 
        unsigned int 
        signed short
long 
        unsigned long
        signed long
long long 
        unsigned long long
        signed long long

看到这里,我想肯定会有小伙伴问为什么 char 也属于整形家族,嘿嘿,那是因为 char 类型在内中存储的其实是字符对应的ASCII值,ASCII值也是整数,所以字符类型也归类到整形家族。


对于 unsigned (无符号)和 signed (有符号):生活中有些数值是有正值和负值,如温度,我们要用有符号类型来存储,我们在使用有符号类型时,signed 是可以省略不写的,例如 int 等同于 singed int ,但是要使用无符号类型时,unsigned 是不可以省略的,例如 unsigned int 。


这里值得注意的是,对于 char 类型来说:只写 char 到底是 unsigned char 还是 signed char C语言是没有明确规定的,只是有些编译器做了规定,如在VS编译器上,char 就等同于 signed char 。


2.整形在内存中的存储

变量的创建是要在内存中开辟空间的,空间的大小是根据不同的类型而决定的。


下来了解下面的概念∶


原码、反码、补码:


计算机中的整数有三种表示方法,即原码、反码和补码。


三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示"负”,而数值位三种表示方法各不相同。


原码:


直接将二进制按照正负数的形式翻译成二进制就可以。符号位是二进制的第一位。


反码:


将原码的符号位不变,其他位依次按位取反就可以得到了。


补码:


反码加一就得到补码


正数的原、反、补码都相同。


对于整形来说:数据存放内存中其实存放的是补码。为什么呢?


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


看下面一段代码:


在有符号类型中,二级制表示形式的第一位是符号位,后面位是数值位。

int main()
{
  int num = 10;//创建一个整型变量,叫num,这时num向内存申请4个字节来存放数据
  //整形4个字节 - 32个bit位(二进制位)   正整数原码,反码,补码相同
  //00000000000000000000000000001010 - 原码
  //00000000000000000000000000001010 - 反码
  //00000000000000000000000000001010 - 补码
  //0x 00 00 00 0a  16进制   a是10
  int num2 = -10;
  //10000000000000000000000000001010 - 原码
  //11111111111111111111111111110101 - 反码
  //11111111111111111111111111110110 - 补码
  //0x FF FF FF F6  //16进制
  return 0;
}

我们可以通过调试,来查看内存中的数据,通过&num来得到num的地址,不知道如何调试的可以看我上篇关于如何调试的文章。 0x是表示后面的数是16进制数。


本质上内存中存放的是二进制,但VS为了方便显示,显示的是16进制,一个16进制位等于4个二进制位,因为16等于2的4次方。并且还可以发现数据在内存中是倒着存放的,关于为什么后面会讲。

可以发现-10在内存中存放的是一个很大的数,也就是-10的补码。其实对于正整数来说,存放的也是它的补码,只不过是补码,反码,原码相同。


内存中的计算也是通过补码计算的:


上面说到计算机只有加法器,所以当我们计算减法时,是先将被减数换成负数再相加。


下面举一个例子计算1-1,换成加法就是1+(-1)


我们可以先用原码计算一下


00000000000000000000000000000001    1的原码

10000000000000000000000000000001   -1的原码


相加,符号位也相加,得到:

10000000000000000000000000000010    -2


可以发现原码相加的得到是-2,是错误的。下面用补码计算


00000000000000000000000000000001 1的补码


10000000000000000000000000000001   -1的原码  我们把它转换为补码


1111111111111111111111111111111111110   -1的反码  (符号位不变,其他位按位取反)

1111111111111111111111111111111111111   -1的补码  (反码加一)


将1的补码与-1的补码相加得到:

100000000000000000000000000000000  变成33位 要去掉最高位


00000000000000000000000000000000    0  得到0


关于从补码得到原码,有两种方式:


1.通过 原码符号位不变,其他位取反得到反码,反码+1得到补码 这种方式反着推


2.补码 符号位不变,其他位取反,再+1就可得到原码,可以发现与原码得到补码得方式相同,想不到吧,这就是计算机发明者的智慧。


原码,反码,补码之间关系如下图所示:  


这里就可以理解为什么补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路了。


3.大端小端存储

什么是大端小端∶


又称大端小端字节序,是以字节为单位,讨论存储顺序的,8个比特位(二进制位)或两个16进制是一个字节


大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;


小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。为什么有大端和小端∶


为什么会有大小端模式之分呢?


这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。


例如一个16bit的short类型x变量,在内存中的地址为0x0010,x的值为0x1122,那么0x11为数据的高位,0x22为数据的地位。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。大端小端存储是由自己电脑的硬件来决定的。


百度2015年系统工程师笔试题︰

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)

 

#include<stdio.h>
int main()
{
  int a = 1;
  char b = *(char*)&a;//&a 是int *类型。
  if (b == 1)
  {
    printf("小端字节序存储\n");
  }
  if (b == 0)
  {
    printf("大端字节序存储\n");
  }
  //printf("%d", b);
  return 0;
}


 

4.练习

这里一起做做些练习,看看自己掌握的怎么样,顺便再讲一些干货。

#include<stdio.h>
int main()
{
  char a = -1;
  //-1是整数发生截断
  //11111111111111111111111111111111 -1补码
  //11111111 a截断
  signed char b = -1;
  //与a相同
  unsigned char c = -1;
  //11111111111111111111111111111111 -1补码
  //11111111  c截断
  //%d 十进制打印有符号整形数据,发生整型提升
  //整型提升时有符号高位补符号位,无符号补0
  //a 11111111111111111111111111111111补码
  //  11111111111111111111111111111110 反码
  //  10000000000000000000000000000001  -1 原码打印
  //b 和a相同
  //c 111111111  无符号高位补0
  //  00000000000000000000000011111111 255
  printf("a=%d b=%d c=%d", a, b, c);  -1 -1 255
  return 0;
}


2.

#include<stdio.h>
int main()
{
  char a = -128;
  //-128
  //10000000000000000000000000001000000  -128原码
  //11111111111111111111111111110111111   反码
  //11111111111111111111111111111000000   补码
  //10000000//截断
    //打印整形提升
  //11111111111111111111111111111000000
  //补码按%u无符号打印  认为是无符号数,原反补相同,打印一个很大的数
  printf("%u\n", a);
  return 0;
}


3.  与第2道结果相同

#include<stdio.h>
int main()
{
  char a = 128;
    //截断后是与-128截断后相同
    //所以结果也与-128相同
  printf("%u\n", a);
  return 0;
}


4.

#include<stdio.h>
int main()
{
  int i = -20;
  unsigned int j = 10;
  printf("%d\n", i + j);
  return 0;
}

写到这里是不是有些不敢写-10了,没关系我们来推理一下


10000000000000000000000000010100  -20原码

111111111111111111111111111111101011  -20反码

111111111111111111111111111111101100  -20补码



00000000000000000000000000001010  10补码 与原码相同 与signed int 相同



111111111111111111111111111111110110  i + j  是补码

10000000000000000000000000001001       反码

10000000000000000000000000001010  -10   原码


5.

#include<stdio.h>
#include<Windows.h>
int main()
{
  unsigned int i;//i恒大于0
  for (i = 9; i >= 0; i--)//会死循环 打印很大的数
  {
    printf("%u\n", i);
  }
  return 0;
}


那 i==0 后再 -1 是谁呢。 我们可以看一下 char 类型, -128到127

可以发现开始了循环,同理,当类型位为 unsigned char 时,最大值为255,在加一就是0。

知道了这一点,我们来做一下最后一道题

6.

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

strlen是求的是 '\0' 之前的字符个数,而'\0'的ASCII码值是0,所以这里计算的是0之前的数组元素个数。


我们可以看 a 数组中的内容 -1,-2,-3 .... -127,-128,因为是char类型的数组,根据上面图片,所以后面是127,126,125 ...2,1,0,-1,... 之后再循环,我们统计 0 之前的元素个数,有128+127=255个,所以答案是255。


本篇结束,下篇讲浮点型数据在内存中的存储

相关文章
|
4月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
442 0
|
2月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
104 1
|
2月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
2月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
2月前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
44 4
|
2月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
60 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
2月前
|
存储 机器学习/深度学习 人工智能
数据在内存中的存储
数据在内存中的存储
|
2月前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储
|
2月前
|
存储
整型在内存中的存储
本文详细解释了计算机中整型数据的三种二进制表示方法:原码、反码和补码,并展示了如何将正数和负数的原码转换为反码和补码。
40 0
|
4月前
|
存储 监控 Docker
如何限制docker使用的cpu,内存,存储
如何限制docker使用的cpu,内存,存储