C生万物 | 深度挖掘数据在计算机内部的存储

本文详细介绍了C语言中整型和浮点型数据在内存中的存储方式,包括原码、反码、补码的概念和转换,以及大小端存储模式。文章通过实例分析了有符号和无符号数据的范围,探讨了整型提升和浮点数的存储规则,并提供了多道经典笔试题来巩固理解。

在这里插入图片描述

在这里插入图片描述
本篇博客全站热榜排名:3
本地图片
本地图片

一、前言

在之前,我们学习了有关C语言中的各种数据类型以及它们的存储空间大小,如下图所示

在这里插入图片描述

类型的意义:

  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)
  2. 如何看待内存空间的视角

二、类型的基本归类

接下去我将上面的这些类型做一个分类,大致分为以下5类

1、整型家族

首先看到的是【整型家族】,分别有charshortintlong

在这里插入图片描述
可能有些同学看到上面的这些很多类型有点懵,什么signedunsigned,下面我就为你来先做一个解答🔍

👉char为何归到整型家族?

  • 因为char在字符存储的时候存的是一个ASCLL码值,而ASCLL码值是一个整数

👉为什么有unsigned和signed两个不同的类型呢

  • 因为数值有正数和负数之分
    • 有些数值只有正数,没有负数(身高)—— unsigned
    • 有些数值,有正数也有负数(温度)—— signed

👉子分类后面的[int]是什么?

  • 因为像shortlong这些都是属于整型的范畴,其实应该写成【signed short int】和【unsigned short int】这样,只是为了简写忽略了后面的int

👉[char]、[signed char]、[unsigned char]这些该如何区分?

  1. char 分为【char】、【signed char】、【unsigned char】
  2. short 分为【short == signed short】、【unsigned short】
  3. int 分为【int == signed int】
  4. long 分为【long == signed long】

2、浮点数家族

浮点数只分为两类,一个是【float】,一个则是【double】,这里只是做介绍,下文会专门介绍浮点数在内存中的存储

在这里插入图片描述

3、构造类型

有关构造类型的话就分为以下这四种,对于【结构体】、【枚举】、【联合】这里不再细说,会专门开章节叙述

在这里插入图片描述

  • 主要的话是要提一嘴这个数组类型。例如看到下面的这三个数组,它们都是互不相同的,只要你修改了它的元素类型或者是元素个数,那这就是个不同的数组

在这里插入图片描述

4、指针类型

接下去是指针类型,对于intcharfloat这三种类型的指针我们之前都见到过,但是可能有同学没有遇见过这个void类型的指针

在这里插入图片描述

  • 它叫做【空指针】
    • 对于int类型的指针可以用来接收int类型的数据的地址
    • 对于char类型的指针可以用来接收char类型的数据的地址
    • 对于float类型的指针可以用来接收float类型的数据的地址
  • 对于void类型的指针可以用来接收任何类型数据的地址【它就像一个垃圾桶一样,起到临时存放的作用】

5、空类型

void 表示空类型(无类型)

通常应用于函数的返回类型、函数的参数、指针类型

三、整型在内存中的存储【⭐】

接下去我们来聊聊有关整型的数据在内存中的存储形式

1、原码、反码、补码

对于原码、反码、补码来说我们之前在学习【操作符】的时候有遇到过,这么我们再来正式地介绍一下

① 概念介绍

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

  • 三种表示方法均有符号位数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位
    • 正数的原、反、补码都相同
    • 负整数的三种表示方法各不相同

接下去就来分别讲讲正数和负数的原、反、补码有什么不同

int a = 10;
  • 对于正数说,因为原、反、补都是相同的,所以当我们写出其原码的时候,其实就可以得出它的反码和补码了

在这里插入图片描述


int a = -10;
  • 对于负数来说就不太一样了,要得到反码就将原码除符号位外其余各位取反,要得到补码的话就在反码的基础上 + 1

在这里插入图片描述
其实除了这三种之外,还有一种叫做【移码】,如果你学习过《计算机组成原理》这门课应该就可以知道移码就是符号位与补码相反,数值位与补码相同。本文不过过多细究

② 原码与补码的转换形式总结

学习了概念后,我们来总结一下有关原码与补码的之间的转换

  • 原码到补码 —— 1种方式
    1. 原码取反,+1得到补码
  • 补码到原码 —— 2种方式
    1. 补码 - 1,取反得到原码
    2. 补码取反,+1得到原码
  • 第1种很直观,我们主要来说说第二种,也就是将补码取反,+1得到原码,回想原码是怎么到补码的,其实你也就学会了补码怎么转换回原码的,只是这一种转换方式大家可能没有怎听说过

在这里插入图片描述

③ 探究计算机内部的存储编码

上面说到了三种整型编码方式,但是真正到了计算机内部使用的是哪个呢?

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

  • 通过去VS中进行调试观察【调试】- 【窗口】- 【内存】就可以看到其实在内存中是以补码的形式存放的

在这里插入图片描述·

  • 但是有同学说:这个f6 ff ff ff是啥呀,怎么就补码了?通过看前面的内存地址可以发现这其实是16进制的表示方式,若是以32位2进制来进行存放的话就太长了,所以采取十六进制的形式
  • 在【进制转换】中,4位二进制表示1位16进制。通过将补码4位4位进行一个划分就可以得出8个16进制的数字为ff ff ff f6,但是仔细一看却可以发现这和VS中我们所观察的结果有所不同,感觉倒了一下【这就要涉及到我们下面所要将的大小端存储

在这里插入图片描述

但是你有疑惑过在计算机内部要以【补码】的形式进行存放,而不是以原码的形式存放呢?

  1. 因为其实很简单,虽然原码的表示形式简单易懂【只需要将真值的+ - 号转换为01即可】,但是原码的加法却异常复杂,需要考虑到两数是同号还是异号以及其他复杂的问题,所以为了解决这些矛盾,人们找到了补码表示法
  2. 在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理
  3. 同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
    • 其运算过程是相同的其实也就印证了我上面介绍的补码转换为原码的第二种方式
  • 虽然在计算机内部是以补码的形式进行存储,但是当其与我们进行交互的时候使用的却是原码的形式。
  • 可能也有同学会疑惑上面的第二点讲【加法和减法也可以统一处理】,我们通过一个最简单的例子就是两数相加+来看看
int a = 1;
int b = -1;
int c = a + b;

printf("c = %d\n", c);
  • 首先,我们写出a与b的补码,因为在内存中要以补码的形式进行存放和计算
int a = 1;
00000000 00000000 00000000 00000001 -//补码

int b = -1;
10000000 00000000 00000000 00000001 - 原码
11111111 11111111 11111111 11111110 - 反码
11111111 11111111 11111111 11111111 - 补码
  • 接下去对这两个补码进行相加,因为二进制逢二进一,所以可以看到最后进位开头多出了一位
int c = a + b;
   00000000 00000000 00000000 00000001
   11111111 11111111 11111111 11111111
---------------------------------------------
  100000000 00000000 00000000 00000000
  • 但是呢,因为c为int类型的整数,所以只能存的下4个字节,也就是32个比特位的数据,所以将最高位【截断】之后剩下的32位全为0
  100000000 00000000 00000000 00000000
   00000000 00000000 00000000 00000000 - 整型只能存放4B,32b
  • 所以可以得出最后的答案为0。在内存中1 - 1 = 0是这样计算的,你明白了吗?

在这里插入图片描述

【总结一下】:

  • 内存中存放的都是补码
  • 整型表达式计算使用的内存中补码计算的
  • 打印和我们看到的都是原码

2、大小端介绍【补码存储的顺序】

① 大小端的由来

我们在开始可以先看这样一个故事

有两个特别强大的国家在过去进行了36个月的战争,在这期间发生了件事情,就是吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,期间发生了多次叛乱,其中一个皇帝因此送命,另一个丢了王位,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的,叛乱平息后,就逃到这个帝国避难。据估计,先后几次有11000余人情愿死也不肯去打破鸡蛋较小的端吃鸡蛋。这个其实讽刺当时英国和法国之间持续的冲突。Danny Cohen一位网络协议的开创者,第一次使用这两个术语指代字节顺序,后来就被大家广泛接受,这个就是关于大端小端名词的由来

  • 看完后可以发现,原来大小端的由来就是因为鸡蛋🥚要从哪头剥引起的

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

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit

  • 上面我们介绍过很多的数据类型,有【char】【int】【double】等等,不过除了8bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),这些数据类型所定义的数值在内存中存放的都超过了1个字节了,要存储到内存中,就有导致一个顺序问题
  • 因为在内存中我们是以字节为单位来讨论数据的存放,就好像下面这个0x12345678在内存中12为1个字节,34为一个字节,56为一个字节,78为一个字节,所以通过右侧的【内存】我们就可以看出虽然呈现的是一个倒着存放样子,但是呢并不是完全倒着,像87 65 43 21,而是78 56 34 12。这就是因为它们整体作为一个字节,讨论的是每个字节顺序,而不是每个字节内部的顺序

在这里插入图片描述
这,也就导致了【大端】和【小端】的由来,接下去呢就正式地来给读者介绍一下这种倒着存放的方式

③ 大(小)端字节序存储

首先来看一下它们的概念,这至关重要⭐

  • 【大端(存储)模式】:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
  • 【小端(存储)模式】:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中;

  • 可以看到,对于下面这一个十六进制数0x11223344,以进制的权重来看的话右边的权重低【0】,左边的权重高【3】,所以11为高位,44为低位。所以若是对其进行小端字节存储的话就要将44存放到低位,11存放到高位,这也就印证了为什么我们最后在看到内存中的存放是
【直流微电网】径向直流微电网的状态空间建模与线性化:一种耦合DC-DC变换器状态空间平均模型的方法 (Matlab代码实现)内容概要:本文介绍了径向直流微电网的状态空间建模与线性化方法,重点提出了一种基于耦合DC-DC变换器状态空间平均模型的建模策略。该方法通过对系统中多个相互耦合的DC-DC变换器进行统一建模,构建出整个微电网的集中状态空间模型,并在此基础上实施线性化处理,便于后续的小信号分析与稳定性研究。文中详细阐述了建模过程中的关键步骤,包括电路拓扑分析、状态变量选取、平均化处理以及雅可比矩阵的推导,最终通过Matlab代码实现模型仿真验证,展示了该方法在动态响应分析和控制器设计中的有效性。; 适合人群:具备电力电子、自动控制理论基础,熟悉Matlab/Simulink仿真工具,从事微电网、新能源系统建模与控制研究的研究、科研人员及工程技术人员。; 使用场景及目标:①掌握直流微电网中多变换器系统的统一建模方法;②理解状态空间平均法在非线性电力电子系统中的应用;③实现系统线性化并用于稳定性分析与控制器设计;④通过Matlab代码复现和扩展模型,服务于科研仿真与教学实践。; 阅读建议:建议读者结合Matlab代码逐步理解建模流程,重点关注状态变量的选择与平均化处理的数学推导,同时可尝试修改系统参数或拓扑结构以加深对模型通用性和适应性的理解。
评论 202
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烽起黎明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值