《C语言深度解剖》(9):深度剖析数据在内存中的存储

2024-04-23 06:12

本文主要是介绍《C语言深度解剖》(9):深度剖析数据在内存中的存储,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🤡博客主页:醉竺

🥰本文专栏:《C语言深度解剖》

😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!


✨✨💜💛想要学习更多数据结构与算法点击专栏链接查看💛💜✨✨ 


1. 数据类型介绍

前面我们已经学习了基本的内置类型:

char        //字符数据类型
short       //短整型
int         //整形
long        //长整型
long long   //更长的整形
float       //单精度浮点数
double      //双精度浮点数
//C语言有没有字符串类型?

以及他们所占存储空间的大小。

类型的意义:

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。

2. 如何看待内存空间的视角。 

1.1 类型的基本归类

整形家族: 

charunsigned charsigned char
shortunsigned short[int]signed short[int]
intunsigned intsigned int
longunsigned long[int]signed long[int]

字符在存储的时候存储的是ASCII码值,ASCII是整数,所以在归类的时候,字符属于整型家族 。

浮点数家族: 

float
double

构造类型:(自定义类型)

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

一个数组,把数组名去掉,剩下的就是这个数组的类型, 元素个数不一样,代表类型也不一样。

eg. int a[10] 和 int a[11]是不同的类型,第一个数组类型是int [10],第二个数组类型是int [11]. 所以说数组也是自定义类型。

指针类型 

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

C语言中所有“变量”的类型判断:把“变量”名去掉,剩下的就是该“变量”的类型。 

空类型: 

void 表示空类型(无类型)

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

2. 整形在内存中的存储

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

那接下来我们谈谈数据在所开辟内存中到底是如何存储的?

比如:

int a = 20;
int b = -10;

我们知道为 a 分配四个字节的空间。 那如何存储? 下来了解下面的概念: 

2.1 原码、反码、补码

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

三种表示方法均有符号位数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位

 正数的原、反、补码都相同。

负整数的三种表示方法各不相同。

原码

直接将数值按照正负数的形式翻译成二进制就可以得到原码。 

反码 

将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码 

反码+1就得到补码。 

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

为什么呢?

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统 一处理;

同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。 

我们看看在内存中的存储:

我们可以看到对于a和b分别存储的是补码。

但是我们发现顺序有点不对劲。 这是又为什么? 

2.2 大小端介绍

什么大端小端: 

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

小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。 

低位:指的是低权值位!

什么是数据的低位高位?

例如:十进制数:1356

1是数据的高位:千位 ;6是数据的低位:个位 

为什么有大端和小端: 

百度2015年系统工程师笔试题: 

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

有两种方法实现:

但是两种方法实现的本质是一样的:选择一个简单的数字(这里选择的是数字1),判断其第一个字节存储的是什么,如果是0就是大端存储模式,如果是1就代表是小端存储模式。 

//代码1
#include <stdio.h>
int check_sys()
{int i = 1;return (*(char*)&i);
}
int main()
{int ret = check_sys();if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}
//代码2
int check_sys()
{union{int i;char c;}un;un.i = 1;return un.c;
}

这里简单解释一下方法2为什么也可以实现,到后面深入学习到联合体的时候会进一步详细讲解,这里可以先掌握方法1.

  1. 定义一个联合体(union),联合体是一个特殊的数据类型,所有成员都从同一内存位置开始,所以同一时间只能存储其中一个成员的值。在这个联合体中,定义了两个成员:一个整数 int i; 和一个字符 char c;

  2. 将联合体 un 的整数成员 i 赋值为1。由于 int 类型通常大于 char 类型,如果系统是32位的话,int 将占用4个字节,这样整数1在内存中的表示(在小端字节序系统中)通常会是 01 00 00 00。也就是说,最低有效字节(最小的地址)存储了值1,而其他字节存储0。

  3. 由于联合体成员共享内存,char c; 将访问整数 i 第一个字节的内容。在小端系统中,由于1被存储在了最低位字节,char c 会读取到值1。在大端系统中,值1会被存储在最高位字节,而 char c 会读取到0。

  4. 函数返回 char c 的值。如果返回值为1,则系统为小端字节序。如果返回值为0,则系统为大端字节序。


2.3 练习(重难点)

  • C语言中的整数类型可以分为两种:有符号和无符号。
  • 有符号整数可以表示正数、负数和零, 而无符号整数仅能表示正数和零。
  • 无符号整数确实采用直接的二进制表示,所有的位都用于表示值,没有符号位。无符号整数不使用原码、反码和补码的概念,因为这些都是有符号整数的表示方法。
  • 在无符号整数中,所有的位,包括最高位,都是数值的一部分,没有一个位是用作符号位的。最高位的1或0并没有特殊的含义,它仅仅是数值的一部分,与其他位一样参与数值的表示。
  • 使用%u格式控制符打印无符号整数时,会直接以其十进制值输出非负整数。
  • 因此,使用%u打印无符号整数时,并不需要考虑原码、补码和反码的问题。以无符号形式打印的结果就是该无符号整数的真实值 。

a是char类型一个字节只能放8个比特位,所以截断了一些比特位,然后再整形提升。

下面程序输出什么?

 

有/无符号位数,是对存储在内存中数据的补码的一种看待(使用)方式!

同一个数字存储在内存中的二进制数字是一样的。

如果是以无符号位数的格式打印%u,那就把内存中数据的补码当成没有符号的数字使用(看待),直接打印出来这串二进制代表的十进制整数即可。

若是打印有符号位数%d,要把补码转换成原码所代表的十进制数字打印出来。

总之,存储内存中的数据是一样的,只不过以不同的方式来使用。 

这段代码看似是想循环打印256次"hello world",但实际上,它会导致一个无限循环,并且会无限地打印"hello world"。原因在于变量i的类型是unsigned char。

在C语言中,unsigned char的取值范围通常是0到255。当i增加到255并执行i++后,按照期望,i会变成256。但是,由于unsigned char的取值上限是255,所以当i尝试增加到256时,它实际上会溢出并回绕到0(这是无符号整数溢出时的正常行为),导致for循环的条件i <= 255始终为真。

这里补充一下char类型的取值范围

3. 浮点型在内存中的存储

常见的浮点数:

3.14159
1E10
浮点数家族包括: float、double、long double 类型。
浮点数表示的范围:float.h中定义

3.1 一个例子 

 浮点数存储的例子:

输出的结果是什么呢?

3.2 浮点数存储规则 

这篇关于《C语言深度解剖》(9):深度剖析数据在内存中的存储的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/927990

相关文章

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

Redis事务与数据持久化方式

《Redis事务与数据持久化方式》该文档主要介绍了Redis事务和持久化机制,事务通过将多个命令打包执行,而持久化则通过快照(RDB)和追加式文件(AOF)两种方式将内存数据保存到磁盘,以防止数据丢失... 目录一、Redis 事务1.1 事务本质1.2 数据库事务与redis事务1.2.1 数据库事务1.

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁

使用JavaScript操作本地存储

《使用JavaScript操作本地存储》这篇文章主要为大家详细介绍了JavaScript中操作本地存储的相关知识,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... 目录本地存储:localStorage 和 sessionStorage基本使用方法1. localStorage

不删数据还能合并磁盘? 让电脑C盘D盘合并并保留数据的技巧

《不删数据还能合并磁盘?让电脑C盘D盘合并并保留数据的技巧》在Windows操作系统中,合并C盘和D盘是一个相对复杂的任务,尤其是当你不希望删除其中的数据时,幸运的是,有几种方法可以实现这一目标且在... 在电脑生产时,制造商常为C盘分配较小的磁盘空间,以确保软件在运行过程中不会出现磁盘空间不足的问题。但在

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11