首页 > 系统相关 >数据在内存中的存储(详细)

数据在内存中的存储(详细)

时间:2024-11-17 13:44:38浏览次数:3  
标签:存储 int 浮点数 补码 内存 详细 bit 整型

目录

在这里插入图片描述

概要:本文主要讲述了数据在内存中的存储位置,存储方法以及如何读取内存中的数据。如果你对其有困惑,不妨好好阅读,也许会有新的体会和感悟。

数据在内存中的存储

1.数据类型介绍

(1)内置类型

整型家族

家族成员 int char unsigned.int short.int long.long unsigned.short signed.short unsigned.long.long signed.long.long signed.int unsigned.char signed.char

疑惑点:为什么char类型属于整型家族?
解释:这是因为char类型的数据在存储的时候,是以它的ASCLL值进行存储的,本质是一个整型。

浮点型家族

家族成员 float double

注意:浮点型都是有符号的。

(2)自定义类型

组成 数组 结构体.struct 枚举.enum 联合体.union

(3)指针类型

组成 一级指针 用来存放内置数据类型变量的地址 二级指针 用来存放一级指针变量的地址

(4)空类型(void)

常用于表示函数不需要参数
例:

#include<stdio.h>
int main(void) {
	printf("hello,world!");
	return 0;
}

运行结果:
在这里插入图片描述

当然,这常适用于非主函数

2.数据在内存中的存储

符号位:对于一个有符号的的数来说,最高位就是符号位,c语言中规定1表示负,0表示正。

《1》整型数据在内存中的存储

原码 ,反码 ,补码

原码
把一个十进制数直接转化为它的二进制,就是这个数的原码。
反码
正数:还是原码
负数:符号位不变,其他位按位取反

补码
正数:还是原码
负数:反码+1

signed.int 负数.十进制 原码.二进制 符号位不变,其他位按位取反 反码 加1 补码 正数.十进制

我们要注意:

【1】
对于有符号整型数据:只有屏幕上打印的是原码,而在内存中进行存储和表示的都是补码。为什么?

序号原因
1.使用补码,可以将符号位和数值域统一处理
2.加减法也可以统一用加法处理(CPU只有加法器)

凭什么这么说?
我们可以用一个简单的例子佐证我们的说法
例:写一个代码打印-1+1的结果
代码:

#include<stdio.h>
int main() {
	printf("%d", -1 + 1);
	return 0;
}

运行结果:
在这里插入图片描述

思考讨论
到这里,你可能会想:这不就理应如此吗?但事实上,这是我们c语言设计好的用补码进行运算的结果。不信?你看:如果我们用原码进行计算,我们会惊奇的发现发现:最后的打印结果应为-2。但是这就与常理违背了,-1 + 1 == -2 , 离之大谱!

所以通过这个例子,我们就不难发现:对于有符号整型数据来说,只有屏幕上打印的是原码,而在内存中进行存储和表示的都是补码的智慧和原因

【2】

序号注意
1.原码,反码,补码的概念主要针对有符号的整型家族(signed int或int)类型的数据,而对于其他类型的数据,有其相应的存储方式
2.特殊地:我们把无符号的整型家族可以看做正数(有符号的数)进行处理,只不过这个正数没有符号位

《2》整型数据的运算与打印

但是,掌握了以上的知识点之后,我们其实还并不能完全预见和理解整型数据运算屏幕上打印的结果,还得掌握一些占位符的作用,整型提升数据截断数据范围
【1】
占位符

占位符作用
%d打印有符号整型数据
%u、%zd打印无符号整型数据

例:

#include<stdio.h>
int main() {
	int a = 2147483647;
	int b = 1;
	printf("%d %u", a + b, a + b);
	return 0;
}

运行结果:
在这里插入图片描述

思考:为什么同样是a + b,打印的结果确一正一负呢?
这里其实涉及了后文要讲的数据范围,但我们通过这个例子依旧是能够感受到占位符不同所带来的不同打印结果,该题我们在后文再进一步剖析。

【2】
数据范围
我们知道:
1.每个数据类型都有其对应的字节长度,1字节 == 8bit 位
详见:

数据类型字节
char1
short(int)2
(long) int4
float4
long long8
double8

那这些bit位是用来干嘛的呢?

2.实际上,这些bit位就是用来存放数字1或0来表示数据大小的。由此我们可以想到:那既然表示数据大小的位数是有限的,那每个类型的数据大小也应该是在一个范围内的。
详见:

数据类型范围
char-128 ~ 127, 0 ~ 255
short(int)- 2^15 ~ 2^15 - 1, 0 ~ 2^16 - 1
(long) int- 2^31 ~ 2^31 - 1, 0 ~ 2^32 - 1

注:有负号的为有符号数据类型数据的范围

那如果一个数据超过了其范围,怎么计算?

3.我们可以用圆环的思想去考虑:
每一个无符号的数据类型从大到小都是bit位全0–>bit位全1,而到了最大的时候,也就是bit位全1,再加1就会发生数据越位,所有bit位变成全0,然后再由bit全0–>bit位全1,构成了一个头尾相交的圆环。
以unsigned char为例:

11111100 11111111 00000000

等价于:

12 225 0

而每一个符号的数据类型从大到小都是bit位全0–>bit位除了首位全1,而到了最大的时候,也就是bit位除了首位全1,再加1就会变成bit位除了首位全0,然后再变成bit位全1,再加1就会发生数据越位,所有bit位变成全0,然后再由bit全0–>bit位全1,构成了一个头尾相交的圆环。

但是和无符号数据类型不同的是:
有符号的数它的二进制位比无符号的数少了一位数值位,并且我们规定:1000…0000000表示的是最小的负数
但我们应注意:二进制数存储和表示无论是正数还是负数都是采用补码的方式,而只是因为我们把无符号数据看做正数,原码和补码一样,且多了一位数值位才导致了表示和存储方式看起来不同,实际都是一样用补码的形式进行存储和表示

无符号和有符号数的异同详述
不同无符号数多了一位数值位
相同实际都是一样用补码的形式进行存储和表示
重要规定(有符号的数)1000…0000000表示的是最小的负数

以signed char为例:

10000000 11111111 00000000 01111111

等价于:

127 0 -128 -1

了解这些,我们就可以把上面的例子剖析
例子:

#include<stdio.h>
int main() {
	int a = 2147483647;
	int b = 1;
	printf("%d %u", a + b, a + b);
	return 0;
}

运行结果:
在这里插入图片描述

疑惑剖析
为什么用占位符%d打印的结果是负数?(1)十进制:2147483648–>二进制(补码):01111111111111111111111111111111;十进制:1–>二进制(补码):00000000000000000000000000000001;相加得:10000000000000000000000000000000(补码)(2)又用%d进行打印,%d是用来打印int类型数据的,所以我们要把相加得的结果转换为原码打印出来(这里要用重要规定)
为什么用占位符%u打印的结果是正数?(1)同上;(2)又用%u进行打印,%u是用来打印无符号整数的,无符号整数的特别之处——就在于我们是把它看作有符号整数中的正数,就导致:它的原码和反码是一样的,所以打印在屏幕上的数就是相加结果的十进制数
对于占位符的思考占位符给了我们一个理解和读取二进制数(补码)的视角方式

【3】
整型提升

问题答案
什么叫整型提升?c语言的整型算术运算总是至少缺省(默认)整型类型的精度进行计算。为了获得这个精度,表达式中字符型(char)和短整型(short)在使用之前就被系统自动转换为普通整型(int),这种转换就叫整型提升
整型提升的意义是什么?表达式的整型运算要在CPU的加法器和其他运算器件中进行,而该运算器的操作数的字节长度就为int的字节长度
数据提升的条件和对象发生在小于整型类型的数据类型上

提升规则

对象 有符号数 正数 高位补0 无符号数 看补码 最高位为1则高位补1.否则补0 负数 高位补1

本质就是看原码的最高位是1还是0,是1补1,是0补0

【4】
数据截断

问题答案
什么是数据截断?就是一个字节长度较小的数据类型在接收一个字节长度较大的数据类型时只会从字节长度较大的数据的数据低位开始接收,直至填满自己的所有二进制位
会有什么结果?可能会导致数据的丢失,一定会导致字节的减少

熟练掌握这些,我们就基本能预见和理解整型数据运算屏幕上打印的结果,下面就以一些例子来加深和巩固对以上知识的理解

例:

#include<stdio.h>
int main() {
	char a = 3;
	//00000011
	char b = 127;
	//01111111
	char c = a + b;
	//因a和b参加运算,所以要发生整型提升
	//整型提升规则:高位补符号位(在大部分编译器中,char等价于signed char,即有符号,最高位为符号位,范围为:-128~127;无符号的char类型数据范围为:0~255;
	//整型提升:
	//00000000000000000000000000000011
	//00000000000000000000000001111111
//结果00000000000000000000000010000000
	//截断(发生在高字节类型转化为低字节):
	//10000000              补码
	//10000011
	printf("%d", c);
	//打印结果为:-126
	return 0;
}
//也可以用圆环秒解:0 1 2 ~ 126 127 ~-1 -2 -3 ~ -127 -128 ~ 0 1 2 ~~

注释的解析很全,不用再过多解释,对照上面的知识点进行理解

《3》大小端介绍

(1)来历

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

(2)模式介绍及效果
模式(全称)效果
大端字节序存储数据的低位字节存放在高地址
小端字节序存储数据的低位字节存放在低地址

实际效果:
自己模拟
在这里插入图片描述
VS2022上的内存窗口
在这里插入图片描述

(3)模式判断

我们可以借助一个2015年百度的笔试题观察大小端的区别
题目:写一个代码,实现大小端的判断
代码

#include<stdio.h>
int main() {
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1) {
		printf("是小端\n");
	} else {
		printf("是大端\n");
	}
	return 0;
}

思路

序号步骤
1用char*类型的指针一个一个字节地访问int类型变量a的各个字节
2我们可以把a赋值为只有第一个字节有值的整数,这里赋值为1
3前提:我们知道指针访问内存的习惯是从低地址到高地址
4如果*p == 1,说明低地址存放的字节是低位字节,也就是小端存储
5如果*p == 0,说明低地址存放的字节是高位字节,也就是大端存储

《4》浮点型数据在内存中的存储

引导:
我们先来一个题目激一激
题目——读出下列代码运行的结果
代码:

#include<stdio.h>
int main() {
	int n = 9;
	float* p = (float*) &n;

	printf("%d\n", n);
	printf("%f\n", *p);

	*p = 9.0;

	printf("%f\n", *p);
	printf("%d\n", n);
	return 0;
}

建议思考写下自己的答案再往后进行

运行结果:
在这里插入图片描述

在学习浮点型在内存中的存储之前,你是否只能准确预见第一行和第三行的输出情况?别担心,跟紧步伐,我们来学习浮点型在内存中的存储

正文:

(1)十进制浮点数转换为标准存储的二进制浮点数

第一步:十进制浮点数转换为二进制浮点数

部分转换方法
整数部分同十进制整数转换为二进制整数一样
小数部分根据权重进行配凑,从小数点后第一位2^-1开始
本质都是根据权重的大小不同进行配凑

第二步:二进制浮点数转换为标准储存的二进制浮点数

根据国际标准IEE(电气与电子工程协会)754,任意一个二进制浮点数可以表示成这样的形式:(-1)^S * M * (2)^E

元素含义
S表示符号位,为0则为正数,为1则为负数
M表示有效数字,大于等于1,小于2
E表示二进制下的指数位

例:

十进制浮点数 5.0 101.0 -1的0次方*1.01*2的2次方 S=0 -5.0 -101.0 -1的1次方*1.01*2的2次方 M=1.01 E=2 S=1
(2)标准存储的二进制浮点数的存储规则

第一部分:存储位置

元素存储位置
float32bit位
S存放于第1个bit位
M存放于第2个bit位~第9个bit位,8个bit位
E存放于第10个bit位~第32个bit位,23个bit位
double64bit位
S存放于第1个bit位
M存放于第2个bit位~第12个bit位,11个bit位
E存放于第个13bit位~第64个bit位,52个bit位

实际效果:

在这里插入图片描述

在这里插入图片描述

第二部分:存储的方式

元素存储方式
S无特殊,正常存
M因为总是满足1<=M<2,所以我们进行存储的时候,就不用把1存入,这样就节省了一位bit位,可存的M的范围就更大了
E较复杂,分问题和解决方案两个模块进行讲解
名称内容
问题首先,E在内存中存储时是一个无符号整数,只有浮点数整体才是有符号的,但是我们知道在科学记数法中,E存在有负数的可能
解决方案所以我们规定:给每个实际的E值加127(对于double是1023)再存入bit位中

以float为例:

十进制数 5.0 在内存中的存储的二进制位 00100000000000000000000010000001 -5.0 10100000000000000000000010000001

通过以上的两个知识点,我们就能了解够浮点型在内存中是如何存储的,但存进去了,又该怎么读取呢?接下来就来介绍读取的方法

(3)浮点数的读取规则

第一部分:占位符

占位符作用
%f把一个数据的二进制位以单精度浮点数(float)的读取规则进行读取
%1f把一个数据的二进制位以双精度浮点数(double)的读取规则进行读取

第二部分:读取规则

以E的值划分为三种情况读取规则
E不为全0或不为全1(1)先把标准存储的二进制浮点数的形式读出来:S:是0真实值符号位就为1(正数),是1真实值符号位就为-1(负数);E:先把对应区域的二进制转换成十进制数,再-127(double类型则-1023)。M:直接把对应区域的二进制转换成十进制数,再在前面加上1和小数点;(2)最后:根据十进制浮点数转换为标准存储的二进制浮点数逆推回去得到真实值(十进制浮点数)
E全为1无穷
E全为0无限趋近于0

学完了这些,我们再回头看看引导的那道题
题目——读出下列代码运行的结果
代码:

#include<stdio.h>
int main() {
	int n = 9;
	float* p = (float*) &n;

	printf("%d\n", n);
	printf("%f\n", *p);

	*p = 9.0;

	printf("%f\n", *p);
	printf("%d\n", n);
	return 0;
}
序号分析
1 int n = 9;首先我们创建了一个整型变量n,并为它分配了4个字节的内存空间
2这4个字节,也就是32个bit位存放的二进制序列为:000000000000000000000000000001001(原码)000000000000000000000000000001001(补码)
3 float* p = (float*) &n然后我们创建了一个浮点型指针变量float* p
4 printf(“%d\n”, n);接着用了%d的占位符对整型变量n的二进制序列(补码)进行访问和打印:%d是将一个二进制数列(补码)当成有符号的整型变量,并转换为原码的十进制打印。类比到这里,就是把00000000000000000000000000001001(补码)按照有符号整型的变换规则变成00000000000000000000000000001001(原码),再转换(十进制)9进行打印
5 printf(“%f\n”, *p);再接着用了%f的占位符对整型变量n的二进制序列(补码)进行访问和打印:%f是将一个二进制数列(补码)当成单精度浮点数,并转换为原码的十进制打印。类比到这里,就是把00000000000000000000000000001001(补码)按照单精度浮点数的读取规则变成(-1)^0 * 1.00000000 * 2^(-118)(二进制浮点数的标准存储形式),再转换(十进制)0.0000000……进行打印
6 *p = 9.0;再然后使用了解引用操作符*,对n的地址中原存的二进制序列00000000000000000000000000001001(补码)以单精度存储规则进行了重新存储:先把我们要存的9.0(十进制浮点数)变成(-1)^0 * 1.001 * 2 ^ 2 (二进制浮点数的标准存储形式),然后再转换为00010000000000000000000000000010(补码)
7 printf(“%f\n”, *p);再接着用了%f的占位符对现在的n中存储的二进制(上述补码)进行访问和打印:%f是将一个二进制数列(补码)当成单精度浮点数,并转换为原码的十进制打印。类比到这里,就是把00010000000000000000000000000010(补码)按照单精度浮点数的读取规则变成(-1)^0 * 1.001 * 2 ^ 2 (二进制浮点数的标准存储形式),,再转换(十进制)9.00000000……进行打印
8 printf(“%d\n”, n);最后用了%d的占位符对现在的n中存储的二进制(上述补码)进行访问和打印:%d是将一个二进制数列(补码)当成有符号的整型变量,并转换为原码的十进制打印。类比到这里,就是把00010000000000000000000000000010(补码)按照有符号整型的变换规则变成00010000000000000000000000000010(原码),再转换(十进制)1096517616进行打印

这样我们就得到了
在这里插入图片描述

总结

本文到这里就结束了,内容较多,看完一定要好好练习,多加体会!
如有错误,还请指正!感谢观看!

标签:存储,int,浮点数,补码,内存,详细,bit,整型
From: https://blog.csdn.net/z15879084549/article/details/143810099

相关文章

  • 力扣825.适龄的朋友,全网最详细解释
    好友请求的总数(LeetCode825)题目描述某社交平台规定,用户A可以向用户B发送好友请求需满足以下条件:用户A的年龄不小于用户B的年龄。用户B的年龄大于0.5*用户A的年龄+7。用户B的年龄小于等于用户A的年龄。此外,一个用户不能向自己发送好友请求。输入:......
  • 【汇编语言】更灵活的定位内存地址的方法(三)—— 不同的寻址方式的灵活应用
    文章目录前言1.比较不同的寻址方式2.问题一3.问题一的分析与求解3.1分析3.1.1数据的存储结构3.1.2分析处理过程3.2代码实现4.问题二5.问题二的分析与求解5.1分析5.1.1数据的存储结构5.1.2分析处理过程5.2代码实现6.问题三7.问题三的分析与求解7.1分......
  • 【汇编语言】更灵活的定位内存地址的方法(二)—— 从 [bx+idata] 到 [bx+si+idata]:让你
    文章目录前言1.[bx+idata]1.1更加灵活的访问内存1.2示例1.3问题一1.4问题一的分析与求解2.用[bx+idata]的方式进行数组的处理2.1问题引入2.2原来的解决方案2.3新的解决方案2.3.1改进后的程序2.3.2还可以写成这样2.3.3用C语言来描述看看2.4比较与总结3.......
  • 2021陇剑杯-内存取证
    陇剑杯2021内存分析(问1)网管小王制作了一个虚拟机文件,让您来分析后作答:虚拟机的密码是_____________。(密码中为flag{xxxx},含有空格,提交时不要去掉)。mimikatz一把梭了flag{W31C0M3T0THiS34SYF0R3NSiCX}内存分析(问2)网管小王制作了一个虚拟机文件,让您来分析后作答:虚......
  • OtterCTF-内存取证
    OtterCTF1-Whatthepassword?mimikatz一把梭了password:MortyIsReallyAnOtter2-GeneralInfo```plainLet'sstarteasy-whatsthePC'snameandIPaddress?让我们从简单开始-PC的名称和IP地址是什么?```vol.py-fOtterCTF.vmem--profile=Win7SP1x64nets......
  • (长期更新)《零基础入门 ArcGIS(ArcMap) 》实验一(下)----空间数据的编辑与处理(超超超详细
    续上篇博客(长期更新)《零基础入门ArcGIS(ArcMap)》实验一(上)----空间数据的编辑与处理(超超超详细!!!)-CSDN博客继续更新        本篇博客内容为道路拓扑检查与修正,有对本实验实验目的、实验介绍有不了解的,可以看下上篇博客。        上篇博客有宝子私信我下载......
  • 2024年数维杯数学建模竞赛 B题:空间变量的协同估计方法研究 问题二 详细思路和代码
    问题2:利用附件1中的数据研究目标变量与协同变量之间的相关性。选择两个协同变量作为目标变量的估计协同变量。目录步骤概述:具体步骤和代码实现:1.加载数据2.探索性数据分析(EDA)3.计算相关性4.选择协同变量5.建......
  • WEB开发-超详细idea配置jsp开发环境中文版
     超详细式idea配置jsp开发环境中文版前提:先得下好jsp运行环境jkd以及tomcat !!!下完了再看本篇文章!!!一、配置web文件1.顶层导航栏选择文件,再打开项目结构2.项目设置中找到模块,再点击加号3.点击加号后,点击web选项4.点击完web后,在右下角找到创建工件并点击(这一步要......
  • CODESYS可视化标准计算器制作详细案例(一)
    #制作一个在可视化界面可用于标准计算器功能详细案例#前言:在可视化界面或触摸屏上,很少有带计算器功能的脚本程序,当我们在工控现场需要使用计算器时,往往依靠电脑或手机上的计算器,如果把计算器的功能移值到界面上,或者为PLC提供一个计算公式,那么,非常方便的即时使用,也可以将结......
  • 数据在内存中的存储
    1.整数在内存中的存储:整数的2进制表⽰⽅法有三种,即原码、反码和补码有符号的整数,三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”,⽤1表⽰“负”,最⾼位的⼀位是被当做符号位,剩余的都是数值位。正整数的原、反、补码都相同。负整数的三......