数据在内存中的存储模式【深度剖析】

发布于:2023-01-09 ⋅ 阅读:(604) ⋅ 点赞:(0)

在这里插入图片描述

本期介绍🍖
主要介绍:类型的基本分类,整型在内存中的存储模式,浮点数在内存中的存储模式,为什么会提出原码、反码、补码的概念?什么是大小端存储顺序。👀



一、数据类型介绍🍖

1.1 基本内置类型:🍖

类型名 介绍 存储大小(字节)
char 字符型 1
short 短整型 2
int 整型 4
long 长整型 4/8
float 单精度浮点型 4
double 双精度浮点型 8

注意:
   1. 类型的意义:类型不仅决定了开辟空间时的大小,还决定了看待内存空间时的视角。

   2. long类型在规定大小的时候,只规定:sizeof(long) >= sizeof(int),并没有明确规定到底是4还是8个字节。正真决定其大小的是编译器的平台:在32位平台上long类型大小为4个字节,而在64位平台上long类型大小为8个字节。


1.2 类型的基本分类:🍖

在这里插入图片描述
注意:
   1. C标准规定:short、int、long默认都是有符号的,但char到底是有符号还是无符号的标准未定义,完全取决于编译器的实现(VS编译器上char是有符号的)。

   2. 构造类型(自定义类型):就是一种可以自己创建或者定义某种新类型的类型。就譬如数组,它的类型会随着数组元素的类型和数组元素的个数的变化而变化着,而元素的类型和个数是由我们所决定的。

   3. float类型的精度要比double类型的精度要低(注意这里的精度指的是存储值与真实值的接近程度)。浮点型往往不能够准确的存放数据,而是以无限接近的方式来存放的。

   4. void类型又称:空类型 / 无类型,通常应用于数组的返回类型、数组参数的类型、指针的类型。


二、整型在内存中的存储模式🍖

2.1 原码、反码、补码🍖

  我们知道数值的表示形式有:二进制、八进制、十进制、十六进制,而在计算机中二进制又存在三种表示形式:原码、反码、补码。三种表示形式均有符号位数值位两部分,二进制序列中的最高位就是符号位(若为0则表示正,若为1则表示负)。
  注意:正数的原码、反码、补码相同。负数的原、反、补并不相同,需要通过一系列计算才能得到。计算规则为:

原码: 将数值直接转换成二进制形式就行。
反码: 原码的符号位不变其他位按位取反就可以得到反码。
补码: 反码+1就可以得到补码。

  那整型到底是以什么样的模式存放的呢?通过调试中的内存我们就可以得知,如下图所示:
在这里插入图片描述
  由此可以得出一个结论:整数在内存中是以补码的形式进行存放的。(备注:该结论非常重要)


2.2 为什么要以补码的形式进行存放?🍖

  在计算机系统中整数一律用补码来表示和存储,原因在于:

  1. 使用补码,可以在运算的时候将符号位和数值域统一处理,同时加减法也可以统一处理。

  举个例子:如果不是以补码的形式存放,而是以原码的形式进行存放。那在计算(1)+(-1)的时候我们就无法得我们所认为的结果。原码是无法进行负数的运算的,但补码却可以。如下图所示:
在这里插入图片描述
  补码的提出顺带着解决了另一个问题:CPU只有加法器。那CPU在读取完数据后如何进行减法操作呢?只需要将减法运算转换成加负数的运算不就行了。

  1. 补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

  什么意思呢?举个例子:如下图所示
在这里插入图片描述
  我们知道原码通过先按位取反再+1操作后可以得到补码,按道理补码只有反过来先-1再按位取反操作后得到的结果才会与原码相同。可事实上,补码完全可以使用原码转换成补码的方法来求原码(即:补码先按位取反后再+1),求得的结果完全与原码相同。
  注意:这是补码的一种特性,是设计之初就存在的,没有必要深究为什么会出现这种情况。

  1. 如果内存是以补码的形式存放,可以保证数据在整型提升前与提升后表达的值不变。

  之前《隐式类型转换》一章中详细介绍了整型提升,这是链接:隐式类型转换

  看到这不禁感叹,在计算机设计之初到底是如何想到补码的,太牛逼了,向大神致敬!!!


2.3 有符号数和无符号数🍖

  有符号数二进制序列的最高位是符号位。而无符号数的最高位不是符号位,而是有效的数值,因为无符号数是不可能出现负的情况,所以没有必要浪费一个bit位的精度。故有符号数和无符号数的取值范围如下图所示:

类型 存储大小(bit位) 取值范围
signed char 8 (-27)~(27-1)
unsigned char 8 0 ~(28-1)
signed short 16 (-215)~(215-1)
unsigned short 16 0 ~(216-1)
signed int 32 (-231)~(231-1)
unsigned int 32 0 ~(232-1)
signed long 64 (-263)~(263-1)
unsigned long 64 0 ~(264-1)

  注意:当存放的整型数值超过类型的存储范围时,就会导致数据的丢失。而真正存放进去的数值,只会是一个在该类型存储范围内的数。举个例子:
  如若有一个char类型的变量,初值为0。使得其每次循环只自增1,循环1000次。问该变量会最后的结果?

#include<stdio.h>
#include<Windows.h>

int main()
{
	char n = 0;
	int i = 0;
	for (i = 1; i <= 1000; i++)
	{
		printf("%d ", n);
		n++;
		Sleep(100);
	}
	return 0;
}

在这里插入图片描述
  结果是什么并不重要,重要的是知道了有符号整型存在着这样一种规律,即随着变量不断的自增,它的值只会在其类型的存储范围内无限的循环下去,并不会超出该范围。 同理对于无符号整数来说,其值只会在0~255这个范围内循环下去(转折点为:255自增1变为0)。


2.4 练习🍖

  1. 例一:求下面代码输出结果会是什么?
#include<stdio.h>

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("%d %d %d\n", a, b, c);
	return 0;
}

在这里插入图片描述
  为什么会输出最后输出的-1 -1 255,而不是我们所认为的 -1 -1 -1 呢?
在这里插入图片描述


  1. 例二:计算下面代码输出的结果
#include<stdio.h>

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

在这里插入图片描述
在这里插入图片描述


  1. 例三:计算下面输出的结果
#include<stdio.h>

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

在这里插入图片描述在这里插入图片描述


  1. 例四:执行下面代码会发生什么?
#include<stdio.h>

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

  执行该代码会陷入死循环。因为当 i=0 时,再次自减1得到的 i 并非负数,而是正的4294967295。所以会从4294967295往下减至0以此无限的循环下去。


  1. 例5:
#include<stdio.h>
#include<string.h>

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

在这里插入图片描述
在这里插入图片描述


三、浮点数在内存中的存储模式🍖

3.1 举个例子🍖

  在讲述浮点数的存储模式前,先来看一个例子:
在这里插入图片描述

  可以看出,整型和浮点型在对同一块空间进行读取操作时得到的结果是不同的。可为什么会不同呢?根本原因是由于整型和浮点型向内存中存储时的方式有所不同,向外取时的方法自然也是天差地别的。所以同一块内存空间,不同的读取方法,结果自然大相径庭。


3.2 浮点数存储规则🍖

  根据国际标准IEEE(电气电子工程师学会)754规定,任意一个二进制浮点数V都可以用科学计数法的方式来表示,如:V = (-1)S × M × 2E

  1. (-1)S表示符号位,S为0表示正数,S为1表示负数。
  2. M表示有效数字位(1 <= M < 2)。
  3. 2E表示指数位。

  举个例子:十进制5.5,写成二进制是101.1,科学计数法是1.011 × 22,按照上面的格式可以得知S=0,M=1.011,E=2。由于每一个二进制浮点数都能以上面的格式来表示,故内存中只需要保存这三个符号位就相当于存放了一个浮点数。


3.2.1 IEEE754规定🍖

在这里插入图片描述
  对于float类型,最高位的1位是符号位S,接下来的8位是指数位E,剩余23位是有效数字M。
  对于double类型,最高位的1位是符号位S,接下来的11位是指数位E,剩余52位是有效数字M。


3.2.2有效数字M、指数E的特别规定🍖

  我们知道二进制浮点数的科学计数法中有效数字M总是 1.xxxxxxx 的形式,那么在存储M时1可以被舍去(因为默认第一位总是1),只需存放后面的小数位。至于读取的时候再加上这个1不就行了,这样做的目的是可以节省出一位有效数字。所以IEEE754规定,在计算机内保存M时,只需要存放小数位。读取时,再加上第一位的1。

  指数E是一个无符号的整数(unsigned int)。对于float类型来说,其指数位E占8个bit位,存储范围为0~255。可是要知道指数E是存在负数的可能的,就譬如:二进制的0.00101,它的E = -3。所以IEEE754规定,存入内存中的真实值必须再加上一个中间数。对于8位的E,中间数为127;对于11位的E,中间数为1023。比如,210的E=10,需要加上中间数127,所以存入值为137,即:10001001。

  指数E从内存中取出时还需要分为3种情况:

1. E不全为0或不全为1时
 取出内存中存放的指数E,然后减去中间数(127 / 1023)得到真实值,再将有效数字M前加上第一位的1。

2. E全为0时
 这时浮点数指数E的真实值为 1-127 或 1-1023 。有效数字M不再加上第一位的1,而是而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0很小的数字。

3. E全为1时
 如果这时有效数字M全为0,则表示±无穷大。


3.3 回头再看该例子🍖

在这里插入图片描述


四、大小端字节序🍖

  我们发现之前整型或浮点型在内从中存放时都是倒着存放的,如图所示:
在这里插入图片描述
  这就要说到大端存储顺序和小端存储顺序了。


4.1 什么是大小端🍖

  大端存储和小端存储描述的是:一个数据在内从中到底是以什么样的顺序存放到

  1. 大端存储顺序:
    把一个数据的高位字节序内容存放到低地址处,低位字节序内容存放到高地址处。
  2. 小端存储顺序
    把一个数据的高位字节序内容存放到高地址处,地位字节序内容存放到低地址处。

  所以我们发现在VS编译器上是以小端存储顺序来存放的,也就是倒序存放。如下图所示:
在这里插入图片描述
  注意:大小端是按字节为单位的顺序来排放的,并不是以bit位为单位来排放的。


4.2 为什么存在大小端🍖

  为什么会有大小端之分呢?这是因为在计算机系统中是以字节为单位来存放的,所以对于那些所需空间大于1个字节的变量来说,如:short、int、float、double等等。就会面临一个问题:如何将多字节安排。因此导致了大端存储模式和小端存储模式的出现。

  注意:数据的类型仅仅只决定了数据在内存中的存储模式,并没有决定存储时的顺序,而决定到底是大端存储还是小端存储的是硬件。


4.3 如何判断当前计算机是大端还是小端🍖

  请设计一个小程序来判断当前机器的字节序。

  方法一:

#include<stdio.h>

int check_sys()
{
	int n = 1;
	return *(char*)&n;
}

int main()
{
	int flag = check_sys();
	if (flag == 1)
		printf("小端字节序\n");
	else
		printf("大端字节序\n");
	
}

  方法二:

int check_sys()
{
	union
	{
		int n;
		char c;
	}un;
	un.n = 1;
	return un.c;
}

int main()
{
	int flag = check_sys();
	if (flag == 1)
		printf("小端字节序\n");
	else
		printf("大端字节序\n");
	
}

在这里插入图片描述

这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到