C进阶:数据在内存中的存储

news2024/12/24 10:40:38

深度剖析数据在内存中的存储

  • C进阶:数据在内存中的存储
    • 深度剖析数据在内存中的存储
    • 数据类型介绍
      • 类型的基本归类
        • 整型家族
        • 浮点数家族:
        • 构造类型:
        • 指针类型:
        • 空类型:
    • 整型在内存中的存储
      • 原码、反码、补码
      • 原码反码补码的相互转换
      • 整型数据的取值范围计算
    • 大小端字节序的介绍
      • 大小端字节序的引入
      • 大小端字节序的概念
      • 为什么要有大端和小端之分
      • 大小端的一道练习
    • 浮点数在内存中的存储
      • 常见的浮点数
      • 浮点数存储的引入
      • 浮点数存储规则

C进阶:数据在内存中的存储

深度剖析数据在内存中的存储

本章内容其实更像是一种修炼内功,其实并不一定会考或者怎么样,但是绝对能够让我们的知识面更广,对知识的理解加深,看一些代码能够理解的更加深刻,也可以说提高我们的编程素养。

本节内容重点有以下几个方面:

1.数据类型详细介绍

2.整型在内存中的存储:原码、反码、补码

3.大小端字节序判断以及介绍

4.浮点数在内存中的存储解析

数据类型介绍

我们已经学过了基本的内置类型:

char 字符类型

short 短整型

int 整型

long 长整型

long long 更长的整型

float 单精度浮点数

double 双精度浮点数

要知道的有:

  • 它们每个占内存存储空间的大小

类型的意义:

  • 每个类型开辟内存空间的大小决定了使用范围
  • 决定了看待内存空间里数据的视角

类型的基本归类

整型家族

整型家族有以下类型:

char:(下面解释为什么char类型归到整型家族)

  • unsigned char
  • signed char

short:

  • unsigned short[int]

  • signed short[int]

int:

  • unsigned int

  • signed int

long:(长整型不一定比整型长,范围是大于等于)

  • unsigned long[int]

  • signed long[int]

long long:

  • unsigned long long[int]
  • signed long long[int]

括号里的int是可以省略的,也就是说:

image-20230104184434364

这里还有值得注意的就是char也是归在整型家族里面,这里肯定会有人有疑问,我们来解释一下,还记得我们一开始学C语言就提到的那张ASCII码表吗,里面记录了每个字符的ASCII码值,我们说过计算机中其实是只有0和1两个数字的,为了存储数据我们是为每个字符编了一个值,用这个值来代表字符用来存储。说到这里我想你就理解了为什么char类型要归到整型家族里面,就是因为char类型本质是以ASCII码值来进行存储的。

浮点数家族:

浮点数家族就比较简单了:

  • float
  • double

只有float和double两种,但是你先不要高兴的太早,实际上浮点数的存储是要比整型存储更复杂的,我们后面会详细分析。float和double这两种类型具体区别下面也会解释。

构造类型:

构造类型有:

  • 数组类型
  • 结构体类型 struct
  • 枚举类型 enum
  • 联合体 union

指针类型:

  • int* pi
  • char* pc
  • float* pf
  • void* pv

空类型:

void表示空类型,也叫无类型。

通常用于函数的返回类型,函数参数,指针类型。

接下来就是我们要学习的重点了,整型和浮点型在内存中的存储。

整型在内存中的存储

我们知道一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的 。

我们今天要重点学的就是数据在开辟内存中到底是如何进行存储的。

原码、反码、补码

计算机中有三种表示二进制的方法,即原码、反码、补码。

三种表示方法均有符号位数值位两部分,符号位用0表示正,1表示负。而数值位正数的原反补码均相同。

负数情况稍微有点复杂,

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

在计算机系统中,数值一律按照补码来进行存储,整型也不例外。

原因是:

原因在于,使用补码,可以将符号位和数值域统一处理;

同时,加法和减法也可以统一处理(CPU只有加法器);

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

计算机是计算不了减法的,当我们用原码来计算时,会发现出问题了,

例如1+(-1),

image-20230104200505819

关于原码反码补码的一些相关知识,例如计算等等,我在之前的博客中有详细解释过,需要的可以自行点击链接查看,

C语言操作符大全(其二),隐式转换,整形提升,结构成员,算术转换,保姆式解析_比昨天强一点就好的博客-CSDN博客

C语言操作符大全(其一),细致讲解,C语言底层逻辑剖析,保姆式解析_比昨天强一点就好的博客-CSDN博客

原码反码补码的相互转换

原码反码补码的相互转换可以用一张图来表示:

image-20230104201447566

整型数据的取值范围计算

我们以char类型为例,介绍一下数据类型的取值范围是怎么计算出来的:

image-20230104202524047

上图就是char类型的取值范围怎么就算出来的。还有一个有趣的图来帮助记忆。

image-20230104203146936

大小端字节序的介绍

大小端字节序的引入

我们可以定义两个变量查看一下内存中的情况,

image-20230104195425182

我们将二进制转化为十六进制去查看内存中的数据可以看到是正确的,但是似乎又有点问题,为什么是倒着放的。这就要引出下面我们要学到的大小端字节序了。

大小端字节序的概念

什么是大小端?

  • 大端字节序存储是指以字节为单位,把数据的高位放在内存的低位地址(低地址)中,把数据的低位保存在内存的高位地址(高地址)中。
  • 小段字节序存储是指以字节为单位,把数据的高位放在内存的高位地址(高地址)中,把数据的低位保存在内存的低位地址(低地址)中。

初看概念似乎有些难以理解,数据的高位低位,内存中的高地址低地址,我们可以通过下面一个图来理解一下。

image-20230106101723900

这样我们就理解了,大小端字节序存储到底是个什么东西,并且我们还知道了我们使用的机器是小端存储,一般情况下我们大部分常见的主机都是小端存储模式,而大端字节序主要应用在服务器上,大小端字节序就是用来存放数据的两种方式,数据一定是按照字节为单位存储的,其实本质上来说,甚至可以把乱序存放每一个字节的的数据,但是如果你乱序放入,当你用的时候,还得能够拿出来才可以,那么对于乱序就不太友好了,所以我们最终是采用了这两种方式,那么为什么是这两种而不是只留下一种呢。

为什么要有大端和小端之分

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

例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

这些看一下了解即可,不理解也没关系。就是处理器的位数不同,导致了数据存储的问题,最终留下了大小端这两种存储方式。

大小端的一道练习

百度2015年系统工程师笔试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。

根据我们上面对大小端的理解,我们举一个例子,假设是1,用下图来分析一下,

image-20230106104636800

现在唯一的问题就是怎么拿到第一个字节的内容,这时候我们要想到指针,并且是char* 的指针,指向的是一个字节的内容。想到这里我们的代码就很好写了。

代码如下:

//百度2015年系统工程师笔试题:
//请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。

#include <stdio.h>

int check_sys(int* p)
{
	return *((char*)(p));//先将int*转换成char*截断拿到第一个字节内容,直接解引用返回即可
}

int main()
{
	int a = 1;
	//00 00 00 01
	//01 00 00 00
	int ret=check_sys(&a);//实现判断的函数
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

浮点数在内存中的存储

常见的浮点数

  • 3.14
  • 1E10 //1E10的意思表示为1.0*10^10

浮点数存储的引入

我们来看下面一个浮点数存储的例子,

int main()
{
	int n = 9;

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

}

请问结果是什么,我们来看一下结果。

image-20230106114314639

看到这个结果,num 和 *pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?我觉得大部分人应该都是懵逼的状态,但是不要着急,接下来我们要学习的就是浮点数在内存中的存储。

之所以提出这个例子就是为了让你知道浮点型数据的存储和整型是完全不同的,学习浮点数存储的时候就将整型数据的存储完全舍弃掉。对于浮点数是完全没有什么原码反码补码的概念的。

浮点数存储规则

根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式 :

  • ( − 1 ) S ∗ M ∗ 2 E (-1)^S* M *2^E (1)SM2E

    • (-1)^S代表符号位,S=0时,为正数;S=1,为负数;
  • M表示有效数字,大于等于1,小于2;

  • 2^E代表指数位;

初看很懵,没有关系,我们举例来说明:

image-20230106120026679

这样看来就可以明白任意一个二进制浮点数都可以表示为
( − 1 ) S ∗ M ∗ 2 E (-1)^S* M *2^E (1)SM2E
IEEE 754规定:

对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M

image-20230106120220466

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M

image-20230106121018251

我们以32位平台为例来解释一下浮点数存储模型,

首先看M,M是有效数字有23位,但是我们说过M的范围是大于等于1,小于2,也就是1.xxxxxxxxx,所以第一位必定是1,所以我们将第一位在保存时舍去,只保留后面的xxxxxxxxx,等到读取的时候再把1加上去,这样做的目的是节省一位有效空间,就可以让精度更高一些,本来可以保存23位有效数字,但是这样做的话就可以保存24位有效数字。

指数E的情况就更复杂一些:

首先E是一个无符号整数(unsigned int),所以假设E是8位,那么E的范围是0255;如果E为11位,那么它的范围是02047,但是我们知道在科学计数法中指数是可以出现负数的,所以IEEE 754规定存入内存时E必须再加上一个中间值,对于8位,加上的是127;对于11位,加上的是1023。

例如2^10,E为10存入内存时要存入10+127=137,即10001001。

上面是E存入时的情况,接下来是E取出时的情况:

当E不全为0或者1时:

这时,先计算出指数E然后减去127(或1023),得到的即为E的真实值,再将有效数字M加上第一位的1,之后位数不够补0即可。

当E全为0时:

直接用1-127(或者1-1023)来表示E的真实值,有效数字不再加上第一位的1,直接还原成0.xxxxxx的小数,这样为了表示接近于0的很小的数字。因为一个很小的数再乘以2^(-126)无穷接近于0。

当E全为1时:

如果有效数字全为0,则表示正负无穷大,正负取决于S。因为2^256次方已经接近于无穷大了(32位平台)。

以上就是浮点数在内存中存储的规则,但是只是这样看我相信肯定是懵逼的状态,所以下面我们举例来理解到底是什么意思,之后再回过来理解,

image-20230106135759683

这里是一个非常简单的例子,实际上有很多浮点数是没有办法精确存储的,例如3.14,你是没有办法将它用二进制形式准确表示出来的,所以有时候题目最后的结果要求是小数,在定义时最好就直接定义为浮点数类型,不要习惯整型就偷懒,最后再强制转换,很容易出现精度的问题。

这时候我们再回过头来看一下我们的引入举的例子,我们来解释一下为什么是会出现这种现象。

image-20230106144254166

int main()
{
	int n = 9;
	//9可以表示为
	//0x 00 00 00 09 //十六进制
	//0000 0000 0000 0000 0000 0000 0000 1001 //二进制
	// 
	//s=0,e=00000000,m=00000000000000000010001
	//e全为0,e=1-127=-126,所以表示为0.xxxxxxx,不再补有效位的第一个1,
	//直接写成(-1)^0*0.00000000000000000001001*2^(-126)
	//这个数非常非常非常小,所以以%f的形式去打印打印出来的当然是0了
	//
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat); //以浮点数的角度看待打印出的是0
	

	*pFloat = 9.0;
	//将9.0去还原出它的二进制1001.0
	//表示为 (-1)^0*1.001*2^3
	// s=0,m=001(舍弃前面的1),e=3
	// e存入的是3+127=130 即10000010
	//0 10000010 001 00000000000000000000 //9.0的二进制序列
	//用整型的角度来看待这个二进制序列,转换成原码
	//00111110111011111111111111111111
	//这就是一个相当大的数字了
	//
	printf("num的值为:%d\n", n);//以整型的角度去看待浮点数打印出特别大的一个值
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;

}

这样我们就彻底理解了浮点数在内存中的存储规则。还是开头说的,其实这部分内容真的很难考到,学习这些知识更像是一种修炼内功,让我们理解的更加透彻,拓展我们的知识面,如果遇到这样的现象能够去解释,关于素养的这种实际价值就靠自己体会了。好了,以上就是C进阶数据在内存中的存储内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/144494.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于WebRtc的web播放大华海康rtsp视频流(延迟一秒以内)

基于WebRtc的web播放大华海康rtsp视频流&#xff08;延迟一秒以内&#xff09;WebRtc下载WebRtc运行Rtc测试Rtc使用html播放需要在vue上播放的可以看下一篇文章WebRtc你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可…

谷粒学院——第十七章、课程微信支付

需求分析 需要实现的功能 需要提供的接口 后端实现 创建数据库表 创建 service_order 模块 引入依赖 <dependencies><dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>0.0.3</ve…

elasticsearch 7.9.3知识归纳整理(六)之 kibana图形化操作es指南

kibana图形化操作es指南 一、创建用户&#xff0c;角色和权限指引 1.创建角色 1.1 在kibana首页点击Manage and Administer the Elastic Stack下的securitys settings 1.2 点击左侧Security 下的roles 1.3 点击右上角的create role 1.4 输入角色名字 完成后点击下面的create…

华为手表开发:WATCH 3 Pro(3)创建项目以及运行完整流程

华为手表开发&#xff1a;WATCH 3 Pro&#xff08;3&#xff09;创建项目以及运行完整流程初环境与设备创建项目创建项目入口配置项目运行项目报错需要在 Appgallery Connect , 创建项目&#xff0c;然后在项目中登录账号就可以了登录后的最终结果再次点击运行&#xff0c;我们…

通俗易懂的java设计模式(5)-抽象工厂模式模式

什么是抽象工厂模式&#xff1f; 抽象工厂模式&#xff0c;可以说是工厂模式的升级版 关于工厂模式&#xff1a;通俗易懂的工厂模式 抽象工厂&#xff1a;围绕着一个超级工厂去创建其他的工厂&#xff0c;这个超级工厂也被称为工厂的工厂&#xff0c;这个设计模式属于创建型…

【小5聊】回看2022,展望2023,分享我的年度总结和感想,在一个行业十年,坚持下去你就是这个行业的专家

2022年&#xff0c;已成为过去&#xff01;2023年&#xff0c;TA已悄然而至&#xff01; 非常感谢CSDN提供的技术平台&#xff0c;很早就关注了C站&#xff0c;11年的时候&#xff0c;当时用的是163邮箱注册的账号&#xff0c;也是主要用来找资料看文章。 18年的时候&#xff0…

八、k8s 数据存储

文章目录1 数据存储介绍1.1 基本存储1.1.1 EmptyDir1.1.2 HostPath1.1.3 NFS2 高级存储2.1 PV2.2 PVC2.3 生命周期3 配置存储3.1 ConfigMap3.2 Secret1 数据存储介绍 在前面已经提到&#xff0c;容器的生命周期可能很短&#xff0c;会被频繁地创建和销毁。那么容器在销毁时&am…

基础数据结构——队列和栈

目录 一、队列 1、循环队列 2、Python队列的三种实现方式 3、例题——队列操作 4、优先队列 &#xff08;1&#xff09;基本操作 &#xff08;2&#xff09;例题&#xff08;lanqiaoOJ题号1228&#xff09; 二、栈 1、用 list 实现栈 2、用 collections.deque 实现栈…

【知识图谱导论-浙大】第一章:知识图谱概论

背景 2022年&#xff0c;随着在自然语言处理方向的深入&#xff0c;我逐渐开始对知识图谱在问答、搜索、推荐等领域的应用产生浓厚的兴趣。自己也通过书籍、博文、论文等对知识图谱有所了解&#xff0c;也通过中文开放知识图谱对中文知识图谱在各领域的发展有了深刻的认识。知…

将非负整数num转换为对应的英文表达(C++实现)—— 力扣第273号题的加强。

【问题描述】 将非负整数num转换为对应的英文表达式。 (样例1) 输入&#xff1a;25 输出&#xff1a;Twenty Five (样例2) 输入&#xff1a;12,315 输出&#xff1a;Twelve Thousand Three Hundred (and) Fifteen 备注&#xff1a;and可省略 另备注&#xff1a;偶然发…

(八)devops持续集成开发——jenkins流水线发布一个docker版的后端maven项目

前言 本节内容我们使用jenkins的流水化工具发布一个后端docker项目&#xff0c;实现后端项目的容器化部署。在开始本节内容之前&#xff0c;我们需要在生产环境安装好docker环境并且能够联网下载镜像。通过jenkins的流水化工具&#xff0c;实现代码拉取&#xff0c;maven打包编…

【java篇】反射机制简单理解

学到JDBC后&#xff0c;使用到反射机制&#xff0c;所以回顾反射机制相关知识点&#xff1b; 文章目录 文章目录 什么是反射机制&#xff1f; 如何理解反射呢&#xff1f; 总结 一、Java反射机制是什么&#xff1f; 二、Java反射机制中获取Class的三种方式及区别&#xff1f; 三…

【目标检测】EfficientDet

1、论文 题目&#xff1a;《EfficientDet: Scalable and Efficient Object Detection》 论文地址&#xff1a; https://arxiv.org/pdf/1911.09070.pdf 代码地址&#xff1a; https://github.com/bubbliiiing/efficientdet-pytorch 2、摘要 Google Brain团队在CVPR 2020上提出…

Liunx 对函数库的理解

一、前言 我们的C程序中&#xff0c;并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时…

PCB入门学习— CHIP类PCB封装的创建

目录 2.12 原理图PCB封装完整性的检查 3.1 CHIP类PCB封装的创建 学习目录 2.12 原理图PCB封装完整性的检查 然后点接受变更。 www.digikey.com搜索规格的网站。 3.1 CHIP类PCB封装的创建 放焊盘——确定大小——画丝印——确定原点EFC。 创建一个PCB元件库&#xff0c;Ct…

React(coderwhy)- 07(路由)

认识React-Router 认识前端路由 ◼ 路由其实是网络工程中的一个术语&#xff1a;  在架构一个网络时&#xff0c;非常重要的两个设备就是路由器和交换机。  当然&#xff0c;目前在我们生活中路由器也是越来越被大家所熟知&#xff0c;因为我们生活中都会用到路由器&#x…

红黑树:比AVL抽象、自由的、更广泛的近似平衡树

RBT与AVL树的比较 AVL&#xff1a;高度要求差不超过1红黑树&#xff1a;RBT要求最长路径不超过短路径的2倍&#xff0c;不需要像AVL一样太平衡&#xff0c;稍微自由&#xff0c;所以旋转较少。 AVL和RBT树性能比较&#xff1a; 插入同样的数据&#xff0c;AVL树旋转更多&…

本地生活配送行业黑马,带你一键读懂闪飞侠

电商的黄金十年已经过去&#xff0c;本地生活的黄金市场才刚刚开启&#xff0c;本地生活市场的增长对同城配送的影响得有多大&#xff1f;2020年的新冠疫情&#xff0c;爆发了同城即时配送的投资新机遇&#xff01;即时配送用户已超5亿。而随着即时配送行业的广泛应用&#xff…

【 Vue3 + Vite + setup语法糖 + Pinia + VueRouter + Element Plus 第三篇】(持续更新中)

在第二篇我们主要学习了路径别名&#xff0c;配置.env环境变量&#xff0c;封装axios请求&#xff0c;以及使用api获取数据后渲染 Element Plus表格 本期需要掌握的知识如下: 封装列表模糊查询组件实现新增 编辑 删除 模糊查询 重置 功能实现表单校验功能实现组件间传值 下期…

Compose跨平台第一弹:体验Compose for Desktop

前言 Compose是Android官方提供的声明式UI开发框架&#xff0c;而Compose Multiplatform是由JetBrains 维护的&#xff0c;对于Android开发来说&#xff0c;个人认为学习Jetpack Compose是必须的&#xff0c;因为它会成为Android主流的开发模式&#xff0c;而compose-jb作为一…