整形数据和浮点型数据在内存中的存储

目录

  • 前言
  • 数据类型介绍
    • 数据类型的基本归类
      • 整形家族
      • 浮点型家族
  • 大小端字节序
  • 浮点数在内存中的存储和读取
    • 浮点数的存储
    • 浮点数的读取
  • 总结

前言

在我之前的文章里,讲过了整数的二进制表示形式有三种,即原码、反码、补码三种形式以及他们的相互转换方式,具体可见链接: C语言初阶操作符学习笔记,本篇文章将更加深入的讲解数据类型以及他们是如何存储的。

数据类型介绍

数据类型的基本归类

根据数据在内存中的存储方式,我们可以将数据分为两大家族:整形家族和浮点型家族。

整形家族

整形家族包括:char,short,int,long,long long(C99)。由于字符在内存里存储的是他们的ASCII码值,所以将char类型归结到整型家族里;long long 类型是在C99标准中加入的。这五种整形又被细分为:
char:
char / signed char / unsigned char
short(short int):
signed short / unsigned short (short等价于signed short)
int:
signed int / unsigned int (int等价于signed int)
long(long int):
signed long / unsigned long (long等价于signed long)
long long(long long int):
signed long long / unsigned long long (long long等价于long long short)
在我之前的文章里除了char未介绍,其他整型数据类型的大小以及取值范围,我已经介绍过了,链接放在这里(部分数据类型的大小以及转换说明符的用法),现在我重点说一下char类型。
char是和signed char等价还是unsigned char等价,这在C标准里是未定义的,等价于哪一个取决于编译器,本篇文章使用的是VS2022编译器,所以char=signed char。

类型 大小(字节) 数值范围
char 1 -2^7 - 2^7-1

浮点型家族

浮点型家族包括两类:float 类型和 double 类型。

类型 大小(字节)
float 4
double 8

关于这两种类型的精度,取值范围等特性在头文件 float.h中可以找到。

大小端字节序

除了char类型变量只占一个字节的内存大小,其他数据类型都至少占据2个以上的字节大小,又因为在内存里以字节为单位存储数据,那么存储的时候接涉先后顺序,举个栗子:

#include 
int main()
{
	int a = 10;
	printf("%d", a);
	return 0;
}

代码如上,整型变量a存储整数10于其中,即:变量a向内存申请了4个字节大小的内存空间,用来存储00000000000000000000000000001010(10的二进制补码),二进制每八位代表一个字节,总共32位,共4个字节,那么这四个字节在内存里如何存储呢,哪个字节在前,哪个字节在后呢?这就引出的大端字节序和小端字节序存储方式。
00000000000000000000000000001010转化为16进制为:(二进制4个数字相当于16进制的一个数字)0x00 00 00 0a,这里两个数字就代表一个字节,最右边的0a为低位,最左边的00为高位,通过调试观察内存窗口,可以发现:
小端字节序存储
每个地址对应一个内存单元即一个字节,所以我们只用看第一行。内存里是将00 00 00 0a倒着存储的,这种存储方式叫做小端字节序;如果内存里是将00 00 00 0a顺着存储的,我们称之为大端字节序。
由此引出小端字节序大端字节序的定义:
小端字节序:
当一个数据的低位字节内容存储在内存中的低地址,高位字节内容存储在内存中的高地址,这种存储方式叫做小端字节序。
大端字节序:
当一个数据的低位字节内容存储在内存中的高地址,高位字节内容存储在内存中的低地址,这种存储方式叫做大端字节序。

这两种字节序是数据在内存里的两种存储方式,具体是哪一种取决于编译器,通过代码也是很容易测出当前机器是哪种存储方式的,代码如下:

#include 
int main()
{
	int a = 1;
	char* pa = (char*)&a;
	if (*pa)
		printf("小端");
	else
		printf("大端");
	return 0;
}

整形数据和浮点型数据在内存中的存储_第1张图片
了解完整形数据在内存里的存储,浮点型数据在内存里又是如何存储的呢?

浮点数在内存中的存储和读取

我们知道浮点型数据分为两种,一种是float类型,也叫单精度浮点型;还有一种是double类型,也叫双精度浮点型,他们所占据的内存空间前面也提到分别是4字节和8字节,而整形int在内存里也是占据4字节的内存空间,那么他们的写入和读出的方式是相同的吗?下面我就通过代码来说明一下。

浮点数的存储

思考以下代码:

#include 
int main()
{
	int a = 5;
	float* pa = (float*) &a;
	printf("%d\n", a);
	printf("%f\n", *pa);
	*pa = 5.5;
	printf("%d\n", a);
	printf("%f\n", *pa);
	return 0;
}

这个代码运行起来会是什么结果呢?
通过运行代码,可以得到以下结果:
整形数据和浮点型数据在内存中的存储_第2张图片
从结果来看,浮点型数据和整形数据在内存里的存入和读出方式是不同的,那么浮点型数据是怎么存储的呢?
以float类型举例,假如有以下代码:

int main()
{
	float a = 5.5f;
	return 0;
}

5.5写成二进制是这样写的,分为两步:
(1)整数部分:101
(2)小数部分:.1
所以二进制表示为101.1,而在内存里并不是就将101.1存储进去的,而是先把101.1转化为类似(-1)^S * M * 2^E 形式,然后将S,M,E存贮到内存里。
符号S决定了存储的数据是正数还是负数,0代表正数,1代表负数;
有效数字M代表有效数字,其数值范围为:1≤M<2;
指数E为是2的指数,讲道理是有正有负的,但是IEEE 754(电气和电子工程协会)规定在内存里E为无符号数,即非负数,那么在存储E时,对于float类型的数据来说需要加上中间数127,对于double类型的数据来说需要加上中间数1023,然后存储到内存里
我们知道float类型占据4个字节大小的内存空间,double类型占据8个字节大小的内存空间,也就是说float有32个bit位,double有64个bit位,在内存里是这样分配给S,M,E的:
(1)对float类型:float(2)对double类型:
double
S就存0或1,E存储指数,由于M都是1≤M<2,所以存储时只需存储小数点后的数字。前文的例子5.5,二进制为:101.1=(-1)^0 * 1.011 * 2^2,所以内存里存储的就是:0 10000001 01100000000000000000000,16进制表示为:
0x40b00000,调试代码观察内存:
整形数据和浮点型数据在内存中的存储_第3张图片
我使用的是VS2022编译器,该编译器是小端存储,所以需要倒着读,显然和我们分析的一致。

浮点数的读取

讲完存储,接着就到了读取阶段。前文提到在存储M时,会省略小数点前的数字1,存储E时会加中间数,所以读取需要考虑的情况就比较复杂,可以分为以下三种情况:
(1) 当E在内存里不全为1或者0时,读取就是存储的逆向,即读取到E区的值减127或者1023得指数E,读取到M区的值加上整数1为有效数字M;
(2) 当E在内存里全为0时,浮点数的指数E等于1-127,即-126为真实值;有效数字M此时不再加1,而是还原成0.xxxx的小数,这样做是为了表示±0,以及接近于0的很小的数字;
(3) 当E在内存里全为1时,这是如果有效数字M全为0,则表示±无穷大(正负号取决于S)。
讲完浮点数的存储与读取,考虑以下代码:

#include 
int main()
{
	int n = 9;
	float *a =(float*) & n;
	printf("n=%d\n", n);
	printf("*a=%f\n", *a);
	*a = 9.0f;
	printf("n=%d\n", n);
	printf("*a=%f\n", *a);
	return 0;
}

会是怎样的输出呢?
我们来分析一下,首先9时整数,转为二进制表示:
00000000000000000000000000001001(正数原码/反码/补码相同)
16进制表示为:0x00000009
所以第一行打印n=9;
由于是将n的地址经过强制类型转换存储到a中,所以从a的视角看他读取到的就是浮点型数据,按章上文讲的浮点型读取方式,S=0,由于E在内存里为全0,所以E=-126,M=0.000000000000000000001001,转化为十进制可以看到是一个很小的数字,所以第二行打印为0.000000;
由于语句*a=9.0f,所以是将9.0存储在a指向的空间里,9.0写为(-1)^S * M * 2 ^ E形式为:(-1)^0 * 1.001 * 2^(3),S=0,M=1.001,E=3,内存里应该是:
01000001000100000000000000000000,十六进制为0x41100000,那么从n的视角看,内存里存放的是整形数据,所以将按整数的读取方式读取数字,所以第三行打印为1091567616;
第四行打印就为9.000000。整形数据和浮点型数据在内存中的存储_第4张图片

总结

通过分析对比整形和浮点型数据在内存里的存储和读取规则,增加我们的内功,进而为我们在以后不论是看代码或者写代码遇到bug时,解决问题的角度就多了一个,久而久之我们也就理解的越来越深刻。

你可能感兴趣的:(学习之路,c语言,学习)