【C语言必学知识点七】你知道在动态内存管理中存在的内存泄露问题吗?遇到内存泄露时应该如何处理?今天跟你好好介绍一下如何正确使用calloc与realloc!!!

动态内存管理——动态函数(calloc、realloc)的使用

  • 导读
  • 一、`calloc`函数
    • 1.1 函数介绍
    • 1.2 `calloc`的使用
    • 1.3 `calloc`与`malloc`
  • 二、`realloc`函数
    • 2.1 函数介绍
    • 2.2 `realloc`的使用
    • 2.3 `realloc`的空间分配
      • 2.3.1 空间分配成功——地址的改变
      • 2.3.2 空间分配失败——内存泄漏
  • 结语

封面

导读

大家好,很高兴又和大家见面啦!!!

在上一篇内容中我们从三个方面介绍了动态内存管理:

  1. 什么是动态内存管理?
    • 对能够进行改变的内存进行管理
  2. 为什么要有动态内存管理?
    • 能够实时的调整内存的大小
  3. 如何进行动态内存管理?
    • 通过动态函数来完成动态内存空间的申请与释放

在动态内存函数中,我们可以将其分为两类:

  • 动态内存申请函数:malloccallocrealloc
  • 动态内存释放函数:free

在上一篇内容中,我们详细介绍了malloc函数与free函数的使用:

  • malloc可以帮助我们申请指定字节的空间:
    • 申请成功时,返回指向该空间的void* 类型的指针
    • 申请失败时,返回NULL
  • free 可以帮助我们释放由malloc申请的空间:
    • free只能释放通过malloccallocrealloc申请的空间
    • free释放的空间大小与申请的空间大小相同
    • free释放的空间为NULL时,不会执行任何操作

按理来说,借助mallocfree就已经能够实现动态内存的申请和释放了,为什么还会存在callocrealloc这两个函数呢?他们又有什么作用呢?他们又应该如何使用呢?在今天的内容中,我们将会对这些问题进行一一的探讨,下面我们就一起进入今天的内容吧!!!

一、calloc函数

callocmalloc一样,都可以用来进行空间申请,但是他们之间还是存在一定区别,为了更好的认识calloc,我们先来看一下calloc的介绍;

1.1 函数介绍

calloc
从函数的介绍中,我们可以提炼出以下信息:

  1. calloc是为数组申请的空间,并且数组中的元素会被初始化为0
  2. calloc会调用malloc来完成空间的申请
  3. calloc在申请空间时需要指定数组元素的个数以及每个元素的大小

单从这些信息,我们是不是可以认为calloc实际上是通过malloc完成内存空间申请,之后再对已申请的空间进行初始化操作。

因此calloc函数的返回值情况应该是与malloc函数的返回值情况一致:

  • 申请成功时,函数返回指向空间的指针
  • 申请失败时,函数返回空指针

接下来我们就来看一下该函数应该如何使用;

1.2 calloc的使用

在探讨函数的使用前,我们还是先来看一下calloc函数的原型:

void *calloc( size_t num, size_t size );

可以看到calloc函数的返回值与malloc一样都是void*类型。

不同于malloccalloc有两个size_t类型的参数,结合前面的介绍,我们可以知道这两个参数分别表示数组元素的个数以及每个元素的大小,比如我要为10个整型元素申请空间,那么对应的参数为:

num = 10;
size = sizeof(int);

当我们通过calloc申请对应空间时,我们就可以将对应参数传入calloc,如下所示:

calloc2
可以看到,此时calloc很好的完成了空间申请与初始化的工作,那既然calloc可以初始化空间,是不是就代表malloc不会初始化空间呢?下面我们就来通过malloc来测试一下:

calloc3
可以看到,通过malloc申请的空间确实不会进行初始化。接下来我们就来对callocmalloc之间的差异做个小结;

1.3 callocmalloc

从函数原型上来看:

  • 相同点:malloccalloc的返回类型都是void*
  • 不同点:
    • malloc有1个size_t类型的参数,表示的是申请空间的字节数
    • calloc有2个size_t类型的参数,第一个参数表示的是元素个数,第二个参数表示的是每个元素的大小

从函数功能上来看:

  • 相同点:
    • malloccalloc都能申请空间
    • malloccalloc的返回值相同
      • 申请空间成功时,返回指向空间的指针
      • 申请空间失败时,返回空指针
    • malloccalloc的返回值都需要进行判空操作
  • 不同点:
    • malloc只负责申请空间,空间中的元素不会进行初始化
    • calloc不仅能申请空间,还会将空间中的元素初始化为0

从底层逻辑上来看:

  • malloc直接向内存申请指定字节数的内存空间,完成申请后会直接返回指向该空间的指针;
  • calloc是通过调用malloc完成空间申请,之后在对申请好的空间进行初始化,最后再返回指向该空间的指针;

从这些差异,我们不难看出,calloc函数实际上就是为了填补malloc函数无法初始化的缺陷,通过calloc函数来申请空间,就能保证在后续对空间的使用中不会出现因为随机值而导致的错误。

现在我们介绍完了calloc函数以及函数的使用,并且还对callocmalloc的差异进行了总结,既然malloc能够申请空间,calloc不仅能申请空间,还能进行初始化,那么为什么还会存在realloc呢?

接下来我们就来认识一下最后一个动态函数realloc

二、realloc函数

在动态内存函数中,realloc的存在让动态内存管理变的更加便捷。

现在有朋友可能会奇怪,这个realloc真的这么神吗?下面我们就一起来看一下realloc的介绍;

2.1 函数介绍

realloc
从介绍中我们可以得到以下信息:

  1. realloc用于重新分配内存块
  2. realloc的返回值有两种情况:
    • 返回值为NULL
    • 返回值为非空指针
  3. 函数的参数分别表示的是指向内存块的指针以及空间的新大小

我们接着往下看:

realloc2
从这次的介绍中我们又可以获取以下信息:

  1. realloc可以改变已经分配好的内存块的大小
  2. 参数memblock表示的是需要改变大小的内存块的起始点:
    • memblockNULLrealloc则执行和malloc同样的操作,申请指定大小的内存空间;
    • memblock不为空指针,则它必须是指向的由malloccalloc或者realloc申请的内存空间
  3. 参数size是内存块的新大小,单位是字节。
  4. 新内存块不一定是memblock指向的空间,该空间可能会移动

看到这里大家可能就会开始疑惑了,为什么新内存块可能会移动呢?别着急,下面我们先来实操一遍realloc函数的用法后再来深入探讨这个问题;

2.2 realloc的使用

首先我们来看realloc的函数原型:

void *realloc( void *memblock, size_t size );

从前面的介绍中我们已经知道了函数的返回值以及参数的含义,这里我们就不再赘述。这里我们需要注意的是memblock这个参数必须是指向由动态函数申请的空间的指针,换句话说就是realloc能够改变的只能是通过动态函数申请的内存空间的大小,如下所示:

realloc3
可以看到此时程序是直接报错的,这个点我们可以理解为:

  1. realloc能够修改的只有能够被改变的空间的大小,不是通过malloccallocrealloc申请的空间的大小是不能被修改的,realloc在对这一类空间进行修改时,程序会出错;
  2. realloc改变空间大小的过程我们可以简单的理解为重新申请一块空间并将源空间中的元素复制到新空间中,最后释放源空间,这个过程我们可以通过malloc或者calloc实现,如下所示:

realloc4
可以看到,整个过程实际上就是执行了3步:申请空间、复制元素、释放空间。这时有朋友可能就会说,那我们重新创建一个数组,不是一样能够达到同样的效果吗?

其实单从过程上来看,他们之间就是存在区别的:

  • 通过动态函数申请的空间,因为可以通过free来主动释放,因此我们经过上述操作后,在内存空间中仍在使用的只有重新申请的空间;

realloc5

  • 通过数据类型创建的数组,因为它的内存空间我们无法主动释放,所以上述过程中并不会执行释放空间的操作,因此最后内存空间中还在使用的是两块空间:
    realloc6

因此对于无法进行大小修改的空间,realloc是无法发挥它的作用的。下面我们就来看一下realloc如何改变空间大小:

realloc7

可以看到,当我们在使用realloc时,realloc会直接在传入的指针p的基础上进行扩容。下面我们接着往下看:

realloc8
可以看到此时realloc是通过额外开辟一块新的空间完成的扩容。也就是说realloc在执行扩容时有两种行为模式:

  • 在源空间上扩容
  • 额外开辟空间扩容

那这两种行为模式有什么区别呢?接下来我们就来深入探讨一下realloc在使用时,内存中的空间的分配情况;

2.3 realloc的空间分配

对于realloc来说,它在执行空间分配时会有两种情况:分配成功与分配失败。下面我们就来分别探讨这两种情况下的空间分配;

2.3.1 空间分配成功——地址的改变

核心:当内存中的空间足够realloc完成空间分配时,realloc的返回值一定是分配好的空间的起始地址。但是当我们在进行空间分配时是执行的扩容操作,那么就会有以下两种情况:

  1. 源空间足够扩容
    realloc会在源空间的基础上直接扩容,该空间的起始地址为原先的起始地址;
  2. 源空间不够扩容
    realloc会在内存中重新申请一块空间,并将原空间中的数据复制到新空间中,之后释放原空间的内存。

这里大家可能不太理解什么是源空间足够扩容和不够扩容,下面我们通过图片来理解,如下所示:
realloc9
从图中可以看到,所谓的源空间足够扩容,指的是在源空间的基础上,能否继续向后开辟连续的空间,或者说,源空间的后面是否还存在空余未被使用的空间。

当空间存在时,我们如果想要继续扩容该空余空间范围内的空间的话,是完全可行的,因此realloc会在源空间的基础上继续向后扩容;

realloc10
可以看到,在这种情况下,源空间后面是没有足够的空间继续扩容的,此时realloc函数便会在有足够空间的位置申请一块新的空间,并将源空间中的数据复制到新的空间中,最后再释放源空间的内存;

从这里我们不难看出,通过realloc进行空间扩容时,函数的返回值不一定是传入的指针所指向的地址,也有可能是移动后的新地址。

基于这种空间可移动的特性,因此当我们传入的指针为一个空指针时,就相当于对一个大小为0且没有任何元素的空间进行扩容,这时realloc就会直接在内存中申请一块大小足够的空间,然后返回该空间的起始地址,这个行为就和malloc一致,也就是说realloc在申请空间时,同样不会对空间进行初始化,如下所示:

realloc11
因此我们可以认为,当realloc需要重新开辟一块空间时,整个过程就好比通过malloc开辟空间:

  1. 在内存空间中申请一块新的空间
  2. 将原空间中的元素复制到新空间中
  3. 释放原空间的内存

现在对空间分配成功的情况我们已经介绍完了,下面我们就来看一下当realloc的空间分配失败时,函数又是如何处理的;

2.3.2 空间分配失败——内存泄漏

核心:在realloc分配空间失败时,会返回一个空指针

realloc申请空间失败时,这里就涉及到一个重要的问题,原空间是如何进行处理的?

在函数的介绍中我们可以看到,当大小为0且缓冲区不为NULL,或者没有足够可用的内存扩充为给定的大小时,返回值为NULL,在这种情况下,原内存块不变。

既然空间申请失败的情况下,原空间是不变的,那么如果我们直接通过指向原空间的指针来接收扩容后的地址,势必就会造成一个问题——空间泄漏

所谓的空间泄漏,我们可以理解为我们在内存空间中申请的空间丢失了,也就是原本指向该空间的指针在空间未被释放前指向了其它内容,导致后续无法找到该空间执行任何操作。

那我们应该如何避免空间泄漏的问题呢?

很简单,我们只需要在进行扩容时通过一个临时的指针来接收realloc的返回值即可,如下所示:

realloc的使用

可以看到,当我们要通过realloc来进行扩容时,我们这里借助了一个临时的指针tmp用于接收realloc扩容后的返回值,这种处理方式能够保证不管内存是否申请成功,我们都能够找到原先的起始地址:

  • 当内存申请失败时,我们可以继续通过指针p来对原型的空间进行操作
  • 当内存申请成功时,指针p指向的内存空间可能被realloc释放掉,我们只需要将指针p指向tmp指向的地址,指针p就能够继续指向完成扩容后的内存空间

结语

今天的内容到这里就全部结束了,在下一篇内容中我们将介绍《柔性数组》的相关内容,大家记得关注哦!如果大家喜欢博主的内容,可以点赞、收藏加评论支持一下博主,当然也可以将博主的内容转发给你身边需要的朋友。最后感谢各位朋友的支持,咱们下一篇再见!!!

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

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

相关文章

14.其他流(下篇)

目录 1. IO流的体系结构 2.字节缓冲流 3.字符缓冲流 4.转换流 5.序列化 6.打印流 7.压缩流与解压流 8.工具包 1. IO流的体系结构 IO流的使用原则:随用随创建,什么时候不用什么时候关闭 1.1 io流的体系结构图 1.2缓冲流的分类 缓冲流,也叫高效流&#…

Redhat 7,8系(复刻系列) 一键部署Oracle21c-xe rpm

Oracle21c-xe前言 无论您是开发人员、DBA、数据科学家、教育工作者,还是仅仅对数据库感兴趣,Oracle Database Express Edition (XE) 都是理想的入门方式。它是全球企业可依赖的强大的 Oracle Database,提供简单的下载、易于使用和功能齐全的体验。您可以在任何环境中使用该…

买家希望信任内容,但他们看重你的内容吗?[新研究]

B2B 技术买家希望获取可信的内容。他们还需要在内容中找到价值。 根据 Informa Tech 的一份 最新研究报告(需注册),许多人发现这种组合难以捉摸。 B2B 营销人员如何填补信任差距,以便买家能够更多地参与、采取更多行动&#xff…

【Python篇】NumPy完整指南(上篇):掌握数组、矩阵与高效计算的核心技巧

文章目录 Python NumPy学习指南第一部分:NumPy简介与安装1. 什么是NumPy?2. 安装NumPy使用pip安装:使用Anaconda安装: 第二部分:NumPy数组基础1. NumPy数组的创建从列表创建一维数组:创建多维数组&#xff…

Java语言程序设计基础篇_编程练习题*18.28 (非递归目录大小)

目录 题目:*18.28 (非递归目录大小) 习题思路 代码示例 输出结果 题目:*18.28 (非递归目录大小) 不使用递归改写程序清单18-7 习题思路 ( getSize方法) 创建一个变量表示总共的大小。传入路径,创建File文件。创建A…

生成式人工智能在无人机群中的应用、挑战和机遇

人工智能咨询培训老师叶梓 转载标明出处 无人机群在执行人类难以或危险任务方面有巨大潜力,但在复杂动态环境中学习和协调大量无人机的移动和行动,对传统AI方法来说是重大挑战。生成式人工智能(Generative AI, GAI),凭…

IPPBX概述

IP PBX涵义 IP PBX是一种电信设备,IP PBX是一种专用交换机(企业内的电话交换系统),用于在本地线路上的VoIP(互联网协议语音或IP)用户之间切换呼叫,同时允许所有用户共享一定数量的外部电话线路…

【Java】网络编程:TCP_IP协议详解(IP协议数据报文及如何解决IPv4不够的状况)

🌈个人主页:努力学编程’ ⛅个人推荐: c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构,刷题刻不容缓:点击一起刷题 🌙心灵鸡汤:总有人要赢,为什么不能是我呢 &#x1f354…

编译和链接以及makefile

编译和链接以及makefile 问题引出,为什么我们会忽略编译和链接这个步骤 一定都会用到但却很少被重视的步骤——编译和链接,通常这两个步骤被我们的IDE封装的很完美,我们一般都是一件构建。 但是一旦遇到错误的时候,尤其是链接相关…

动手学深度学习(pytorch土堆)-05-1神经网络

Neural network 以下是 torch.nn 库中各个组件的详细分类: 1. 容器 (Containers) torch.nn.Sequential: 顺序容器,用于将层按顺序堆叠在一起。torch.nn.ModuleList: 模块列表,用于存储多个子模块。torch.nn.ModuleDict: 模块字典&#xff…

Pycharm出现Please specify a different SDK name报错,但是看不到重名环境解决方案

这句话的意思是出现了重名的环境 ,一般情况下删除重名的环境即可解决问题。做法如下图所示 1,点击右上角齿轮→settings(或者File→settings)进入Python Interpreter 2.点击这个沙漏按键,你会发现多了几个环境&#x…

minio的最大优势--运维(五)

前言: 前面讲了minio的简介、场景、单机部署、集群部署等内容,现在简单来讲讲它的运维工具。 一、Minio客户端使用(这个中文文档没问题) 官方文档地址:https://docs.min.io/docs/minio-client-quickstart-guide.html…

穿什么有这么重要?——装饰模式

文章目录 穿什么有这么重要?——装饰模式穿什么有这么重要?小菜扮靓第一版小菜扮靓第二版装饰模式小菜扮靓第三版商场收银程序再升级简单工厂策略装饰模式实现装饰模式总结 穿什么有这么重要?——装饰模式 穿什么有这么重要? 时…

手写redis实现分布式锁详细教程,满足可续锁、可重入等分布式锁条件

前言 本文将讨论的做一个高并发场景下避不开的话题,即redis分布式锁。比如在淘宝 的秒杀场景、热点新闻和热搜排行榜等。可见分布式锁是一个程序员面向高级的一门必修课,下面请跟着本篇文章好好学习。 redis分布式锁有哪些面试题 1.Redis做分布式的时…

执着追求与匠心独运 朵拉朵尚2024欧洲溯源 深入德国巴斯夫

执着追求与匠心独运 朵拉朵尚2024欧洲溯源 深入德国巴斯夫 前不久,朵拉朵尚踏上了其2024年欧洲溯源之旅的第三站—德国巴斯夫,一场旨在深化护肤智慧、共谋新品未来的深度交流盛宴在此拉开帷幕。作为全球最大的化工公司,巴斯夫不仅以其卓越的…

电脑录屏工具哪个好用?推荐新手几款实用工具介绍

现在不管是录个教学视频教教别人,还是直播游戏给粉丝看,或者是展示你的产品,都得用到它。但是市面上的录屏软件多得让人眼花缭乱,新手可能一看就懵了。别急,今天我就给你介绍几个特别好用的电脑录屏工具,不…

攻克大模型面试!RAG基础与应用痛点一网打尽!

RAG相关理论知识与经验整理。 谈到大模型在各垂直领域中的应用,一定离不开RAG,本系列开始分享一些RAG相关使用经验,可以帮助大家在效果不理想的时候找到方向排查或者优化。 本系列以医疗领域为例,用面试题的形式讲解RAG相关知识…

唤醒金融数据中台:我的数据驱动秘籍

目录 一、明析业务痛点和机会点二、数据驱动精准化营销三、一体化数据平台——整合金融数据1. 数据整合与标准化2. 数据服务与共享3.业务体系集中化 四、强化金融数据安全,筑牢数据保护防线 在当今数字化时代的大潮中,数据无疑是金融行业最耀眼的财富。作…

销售管理中的难题,你是否也深陷其中?

销售管理中的难题似乎总是让人头疼。从客户跟进不及时,到销售数据分析不足,再到团队沟通不畅,导致商机频频流失。销售目标总是难以达成,业绩压力越来越大,但解决方案却总是力不从心。面对这样的问题,你是不…

Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用增强版

前言 之前使用Chainlit集成LlamaIndex并使用通义千问大语言模型的API接口,实现一个基于文档文档的网页对话应用。 可以点击我的上一篇文章《Chainlit集成LlamaIndex并使用通义千问模型实现AI知识库检索网页对话应用》 查看。 本次针对上一次的代码功能进一步的完善…