深入解析内存中的整数与浮点数存储

一、整数在内存中的存储

首先,整数的2进制表示方法有三种,分别是原码、反码和补码。整数在内存中存储的是二进制的补码。补码又分为符号位和数值位两部分,最高位的一位是符号位,用'0'表示正数,用'1'表示负数;其余则是数值位。 不过,我们在调试窗口中观察内存时 ,为了方便展示,显示的是16进制的值 ,如下图所示**。**

二、大小端

从上图中可以看到,为什么我们给n赋值位0x11223344,在内存中观察的时候是44 33 22 11呢?这就要引出新的概念"大小端"了。

所谓大端(存储)模式,是指数据的低位字节内容保存在内存的高地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。而小端(存储)模式是指数据的低位字节内容保存在内存的==低==地址处,⽽数据的==⾼==位字节内容,保存在内存的⾼地址处。

下面我们用几段代码来切实的感受下大小端的不同。

复制代码
#include <stdio.h>
int main()
{
	int n=1; //转为16进制位:0x00 00 00 01  在左边的是高位,右边的是低位
    // 内存               低位 ---> 高位
    // 在大端模式下存储的是:00 00 00 01
    // 在小端模式下存储的是:01 00 00 00
	return 0;
}

从上述代码中可以感受到大端和小端的具体不同之处,那么我们怎么判断所用机器是大端还是小端呢?

复制代码
#include <stdio.h>

int check_sys()
{
	int n = 1;		//十六进制表示为0x00 00 00 01
	return *(char*)&n;	
    //将n的地址强制转化位char*类型,然后解引用,即可取出最低一位地址对应的数字,然后判断它的值即可
	//小端:01 00 00 00 
	//大端:00 00 00 01
}

int main()
{
	int ret = check_sys();	
	if (ret == 1)
		printf("小端\n");	 
	else
		printf("大端\n");	
	return 0;
}

下面我们做个练习来巩固下学习成果吧。(解析在文章末尾

复制代码
#include <stdio.h>
//X86环境⼩端模式下,输出结果是什么?

int main()
{
	int a[4] = { 1, 2, 3, 4 };			
	int* ptr1 = (int*)(&a + 1);			
	int* ptr2 = (int*)((int)a + 1);		
	printf("%x,%x", ptr1[-1], *ptr2);	
	return 0;
}

三、浮点数在内存中的存储

浮点数在内存中存储时首先还是将十进制转换为二进制 ,使用乘2取整法就可以啦(将小数部分不断乘以2,记录整数部分(0或1),直到小鼠部分为0或达到所需精度,最后将整数部分顺序排列)。

浮点数转换为二进制时,小数点后数字的权重分别是2^(-1),2^(-2),2^(-3),......

按照国际标准IEEE 754,我们需要将二进制浮点数V表示为以下形式:

V=(-1)^S * M * 2^E

  1. (−1)^S 表⽰符号位,当S=0,V为正数;当S=1,V为负数

  2. M 表⽰有效数字,M是⼤于等于1,⼩于2的

  3. 2^E 表⽰指数位,E为⼀个⽆符号整数

举两个例子,⼗进制的5.0,写成⼆进制是 101.0 ,相当于 1.01×2^2,可以得出S=0,M=1.01,E=2;⼗进制的-5.0,写成⼆进制是:-101.0 ,相当于-1.01×2^2 。那么,S=1,M=1.01,E=2。

在内存中时按照 S E M 的顺序依次存储的,有以下要求:

1.对于32位的浮点数(float),最⾼的1位存储符号位S,接着的8位存储指数E ,剩下的23位存储有效数字M

  1. 对于64位的浮点数(double),最⾼的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

存储有效数字 M 和指数 E 时,还有一些特别的规定

1.在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的小数部分 。⽐如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去

2.存⼊内存时E的真实值必须再加上⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023

四、浮点数取的过程

浮点数在取时可分为3种情况。

E 不全为0或1时

指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第⼀位的1

E 全为0时

浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第⼀位的1,⽽是还原为0.xxxxxx的⼩数。这样做是为了表⽰±0,以及接近于0的很⼩的数字

E 全为1时

如果有效数字M全为0,表⽰±⽆穷⼤(正负取决于符号位s)

做个题目巩固下浮点数的学习成果吧。(解析在最后)

复制代码
#include <stdio.h>
//输出结果分别时什么呢?

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);				
	printf("*pFloat的值为:%f\n", *pFloat);	

	*pFloat = 9.0;	
	printf("n的值为:%d\n", n);				
	printf("*pFloat的值为:%f\n", *pFloat);	
	return 0;
}

五、题目解析

第一题

复制代码
#include <stdio.h>
//X86环境⼩端字节序

int main()
{
	int a[4] = { 1, 2, 3, 4 };			
    //在小端模式下的存储如下
    //01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00

	int* ptr1 = (int*)(&a + 1);			
    //指针+1取决于指针的类型

	int* ptr2 = (int*)((int)a + 1);		
    //整数+1就是+1,向后走一个字节,跳过01,访问的是00 00 00 02
    //因为是小端模式,所以低地址存的是低字节数据,访问的是20 00 00 00

	printf("%x,%x", ptr1[-1], *ptr2);	//4,2000000
	return 0;
}

第二题

复制代码
#include <stdio.h>

int main()
{
	int n = 9;
	//二进制:1001->补码:00000000 00000000 00000000 00001001
	//用浮点数视角看的话就是:0 00000000 00000000 00000000 0001001
	//E=1-127=-126
	//E全为0,有效数字M,取出后不再加上第一位的1
	//(-1)^0 * 0.00000000 00000000 0001001 * 2^(-126) -> 无限接近于0,看作0

	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);				//9
	printf("*pFloat的值为:%f\n", *pFloat);	//0.000000

	*pFloat = 9.0;	//以浮点数的视角存储9.0
	//(-1)^0 * 1.001 * 2^3
	//S=0,M=1.001,E=3
	// 3+127=130    
    //0    130    3(001)         
	//0 10000010 00100000 00000000 0000000

	printf("n的值为:%d\n", n);				//1091567616
	printf("*pFloat的值为:%f\n", *pFloat);	//9.000000
	return 0;
}
相关推荐
jimmyleeee4 小时前
人工智能基础知识笔记十八:Prompt Engineering
笔记·prompt
Ro Jace4 小时前
模式识别与机器学习课程笔记(11):深度学习
笔记·深度学习·机器学习
Yupureki4 小时前
从零开始的C++学习生活 9:stack_queue的入门使用和模板进阶
c语言·数据结构·c++·学习·visual studio
小小洋洋4 小时前
笔记:TFT_eSPI不支持ESP32C6;ESP8266运行LVGL注意事项
笔记
一念&4 小时前
每日一个C语言知识:C 数组
c语言·开发语言·算法
小年糕是糕手4 小时前
【数据结构】单链表“0”基础知识讲解 + 实战演练
c语言·开发语言·数据结构·c++·学习·算法·链表
聪明的笨猪猪4 小时前
Java JVM “垃圾回收(GC)”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
疯狂吧小飞牛5 小时前
Lua C API 中的 lua_rawseti 与 lua_rawgeti 介绍
c语言·开发语言·lua
ue星空6 小时前
逆向分析光与影:33号远征队使用的UE技术栈
笔记·学习