数据在内存中的存储

一、整数在内存中的存储

整数的2进制表示方法有三种,即 原码、反码和补码
三种表示方法均有符号位数值位 两部分,符号位都是用0表示"正"用1表示"负" ,而数值位最
高位的⼀位是被当做符号位,剩余的都是数值位。
正整数的原、反、补码都相同。
正整数 4: 原、反、补码都相同 00000000 00000000 00000100
负整数的三种表示方法各不相同。

  • 原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
  • 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
  • 补码:反码+1就得到补码。

负整数-4:原码:10000000 00000000 00000100 反码:111111111 111111111 111111011
补码:111111111 111111111 111111110

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

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

二、大小端字节序和字节序判断

当我们了解了整数在内存中存储后,我们调试看一个细节:

调试的时候,我们可以看到0x11223344在内存中的储存是倒置的,这个数字是按照字节存放的,这里是为什么呢?

这里我们就要进入对大小端的学习了

2.1什么是大小端?

其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分 为大端字节序存储和小端字节序存储,下面是具体的概念:

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

为了方便记忆:可以看成 大头儿子和小头爸爸

大头儿子(小端存储): 数据的高位字节内容保存在内存的低地址处。

小头爸爸(大端存储): 数据的低位字节内容保存在内存的低地址处
上述概念需要记住,方便分辨大小端

2.2为什么有大小端?

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

那我们怎样辨别自己的编译器是什么存储呢?曾在百度的笔试题中,则出现了相关的知识点;

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

请同学们思考一下,对于大小端存储的模式来写段代码吧(不能利用监视内存来完成):

复制代码
#include<stdio.h>

int check_sys()
{
	int i = 1;

	return (*(char*)&i);
	//先取出i的地址,再将i的(int*)转化为char*,最后在解引用,取出低地址的一个字节;
}


int main()
{
	int ret = check_sys();
	//返回1,则说明低字节放在低地址
	//返回0,则说明高字节放在低地址
	if (ret = 0) {
		printf("大端存储");
	}
	else {
		printf("小端存储");
	}
	return 0;
}

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

既然我们学会了整数在内存中的存储,那我们来看看浮点数在内存中的存储有什么不一样吧!!
常见的浮点数:3.14159、1E10等,浮点数家族包括: float、double、long double 类型。
浮点数表示的范围: float.h 中定义

来看这一段代码:

复制代码
#include <stdio.h>

int main()
{
	int n = 1;
	float* pFloat = (float*)&n;
    //取地址,强制转化为float*类型
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 1.0;
    //将该地址的值改为1.0
	printf("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}

我们会发现:

num 和 *pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?)

复制代码
n的值为:1
*pFloat的值为:0.000000
num的值为:1065353216
*pFloat的值为:1.000000

根据国际标准IEEE(电气和电子工程协会) 754,任意一个⼆进制浮点数V可以表示成下面的形式:
V = (−1) ^ S(表示-1的S次方) M ^ 2 E(表示2的E次方)

  • (−1)^S (表示-1的S次方) 表示符号位,当S=0,V为正数;当S=1,V为负数
  • M 表示有效数字,M是大于等于1,小于2的
  • 2^ E (表示2的E次方) 表示指数位

例如:

十进制的5.0,写成二进制是 101(整数部分). 0(小数部分) ,相当于 1.01×2^2 。

S = 0; 101.0 相当于1.01*(2^2); 所以M = 1.01; E = 2

在 IEEE 754 规定:

对于32位的浮点数,最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数,最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

对于32位的浮点数:


对于64位的浮点数


那我们就进入浮点数在内存的存储吧

3.1浮点数存的过程

IEEE 754 对有效数字M和指数E,还有⼀些特别规定。
前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。
IEEE 754 规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目 的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保 存24位有效数字。


对于E来讲:情况就比较复杂:

E为无符号整数;

E如果为8位,则取值范围0~255;如果为11位,则取值范围0~2047;但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存⼊内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是 10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

E不全为0或不全为1

这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
比如:0.5 的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其 阶码为-1+127(中间值)=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位 00000000000000000000000,则其⼆进制表示形式为:

0 01111110 00000000000000000000000

E全为0

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

0 00000000 00100000000000000000000

E全为1

这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

0 11111111 00010000000000000000000

那我们再来看看一开始的问题

复制代码
#include <stdio.h>

int main()
{
	int n = 1;
	float* pFloat = (float*)&n;
    //取地址,强制转化为float*类型
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 1.0;
    //将该地址的值改为1.0
	printf("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}

n的值为:1
*pFloat的值为:0.000000
num的值为:1065353216
*pFloat的值为:1.000000

为什么将1还原成浮点数*pFloat的值为0.000000

1 以整型的形式存储在内存中,得到如下二进制序列:

0000 0000 0000 0000 0000 0001

将这段二进制序列,以按照浮点数的形式拆分,S=0,E=00000000;
由于指数E全为0,所以符合E为全0的情况。因此,浮点数V就写成:

V=(-1)^0 × 0.00000000000000000000001×2^(-126)=1.001×2^(-146)
//这里的-126为1-127计算而来
显然,V是⼀个很小的接近于0的正数,所以用十进制小数表示就是0.000000


再看第二个,为什么浮点数1.0,打印成整数为1065353216

⾸先,浮点数1.0 等于⼆进制的0001.0,即换算成科学计数法是:1.00×2^0
S=0;E=0+127;M=0

00111 1111 000 0000 0000 0000 0000 0000

这个32位的⼆进制数,被当做整数来解析的时候,就是整数在内存中的补码

原码:正是1065353216

相信学习了这节,你将会对浮点数有更深刻的了解。

oi!点个赞走吧!!!

相关推荐
一个很帅的帅哥18 分钟前
JavaScript事件循环
开发语言·前端·javascript
驰羽18 分钟前
[GO]gin框架:ShouldBindJSON与其他常见绑定方法
开发语言·golang·gin
摇滚侠18 分钟前
Spring Boot 3零基础教程,WEB 开发 自定义静态资源目录 笔记31
spring boot·笔记·后端·spring
摇滚侠19 分钟前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 遍历 笔记40
spring boot·笔记·thymeleaf
程序员大雄学编程24 分钟前
「用Python来学微积分」5. 曲线的极坐标方程
开发语言·python·微积分
Code小翊30 分钟前
C语言bsearch的使用
java·c语言·前端
坚持编程的菜鸟1 小时前
LeetCode每日一题——三角形的最大周长
算法·leetcode·职场和发展
Jose_lz1 小时前
C#开发学习杂笔(更新中)
开发语言·学习·c#
Chloeis Syntax1 小时前
接10月12日---队列笔记
java·数据结构·笔记·队列
QT 小鲜肉1 小时前
【个人成长笔记】Qt 中 SkipEmptyParts 编译错误解决方案及版本兼容性指南
数据库·c++·笔记·qt·学习·学习方法