【数据结构】动态内存管理函数

动态内存管理

  • 为什么存在动态内存管理
  • 动态内存函数的介绍
    • 🎊malloc
    • 补充:perror函数
    • 🎊free
    • 🎊calloc
    • 🎊realloc
  • 常见动态内存错误
    • 对空指针的解引用操作
    • 对动态开辟空间的越界访问
    • 对非动态开辟内存使用free释放
    • 使用free释放一块动态开辟内存的一部分
    • 对同一块动态内存多次释放
    • 动态开辟内存忘记释放(内存泄漏)

为什么存在动态内存管理

在此之前,我们开辟内存空间有两种方式。一种是创建一个已知类型的变量。
比如说:

int a=10;  //在栈空间上开辟4个字节

向系统申请了4个字节的内存空间。(对于 int型,4个字节它是固定的。)
还有一种是,创建一个数组。
比如说:

int arr[10]; //在栈空间上开辟40个字节的连续空间。

向系统申请了40个字节的内存空间。当这个数组开辟好了空间,没有办法改变它的大小。
对于数组的创建,它的内存开辟方式是比较死板的。

int arr1[10];int arr2[100]

我们创建数组时,在一开始时就会指定数组的大小。arr1的内存空间为40个字节,可以存放10个整型元素。arr2的内存空间为400个字节,可以存放100个整型元素。

但有可能我们在使用数组arr1的时候,需要存放11个数组元素,而没有办法把它边长
我们可能为了尽可能满足很多情况,而创建一个很大的数组arr2,但在实际使用过程中,我们可能只会存放20个元素,而导致了内存空间的浪费

所以这样的内存开辟方式它是固定的,是不够灵活的。 不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才能知道,那么数组在编译时开辟空间的方式就不能满足了。 所以,我们需要学会开辟动态内存

动态内存函数的介绍

malloc
free
calloc
realloc

🎊malloc

malloc函数的原型:

void* malloc(size_t size);

malloc声明在stdlib.h头文件中。
功能:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回指向这块空间的指针。
  • 如果开辟失败,则返回一个NULL指针。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

因此,malloc函数的返回值一定要做检查。

举个例子:
原来,我们用数组栈区开辟内存空间:

现在,我们在堆区动态开辟同样大小的内存空间:

根据malloc函数的原型,我们需要传递一个参数,以字节为单位的内存。

malloc(40)//即开辟了40个字节的内存空间

然后,我们需要一个指针p来指向这块儿开辟好的连续的 内存空间
但由于 malloc函数的返回值为 void*,即无类型指针,所以我们需要先进行强制转换,将无类型指针转换为整型指针
因此,

int* p=(int*)malloc(40);

此时我们,开辟的空间在内存中的堆区的空间,但是指向这块空间的指针是放在栈中的,也就是上面例子中的p指针。
如下图所示。
在这里插入图片描述

但是,正如我们上面所提到的,我们只是用malloc函数向内存申请开辟40个自己的连续空间,不一定开辟成功。所以我们需要利用指针p进行进一步检验

若开辟成功,进行访问:

malloc函数 申请的内存空间,但程序退出时,不会主动释放的,需要使用free函数来释放。

补充:perror函数

perror函数(忘的打印输出函数)

来自这篇博客:C语言perror函数详解

🎊free

C语言提供free函数,专门用来做动态内存释放和回收的。
free函数原型:

void free(void *ptr);

free函数也是声明在头文件<stdlib.h>中的。
free函数用来释放动态开辟的内存。

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,则函数什么事都不做。

🎊calloc

C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配。
calloc函数的原型:

void* calloc(size_t num,size_t size);

calloc函数的功能为,为num个大小为size的元素开辟一块空间,并且把每个字节初始化为0。
与函数malloc函数的区别只在于calloc会在返回地址之前把申请的每个字节初始化为0。
即:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));//开辟10个大小为sizeof(int),即4个字节 的空间//判断是否开辟成功if (p == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++)printf("%d\n", *(p + i));return 0;
}

在这里插入图片描述

🎊realloc

  • realloc函数的出现让动态内存管理更加灵活
    有时候我们发现之前申请的内存过小了,有时候我们发现我们申请的内存过大了,所以,在一些时候,我们需要对内存的大小做灵活的调整。那么realloc函数就可以做到对动态开辟内存大小的调整
    realloc函数的原型如下:
void* realloc(void* ptr,size_t size);

其中,ptr是要调整的内存的地址
size是调整之后的新的内存的大小。
返回值是调整之后内存的起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间。
举个例子:
我们首先使用malloc函数开辟40个字节的空间:

//malloc函数申请空间
int* p = (int*)malloc(40);
if (p == NULL)
{perror("malloc");return 1;
}
//初始化
int i = 0;
for (i = 0; i < 10; i++)*(p + i) = i;

我们想要将这个空间扩大,扩大为80个字节的空间。
于是我们使用realloc函数进行调整。但是内存空间的变化可能有不同的情况。
情况一,后面有足够的空间。
即这样的情形:
要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。

情况二,后面没有足够的空间:
那么就在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

//增加一些空间
int *ptr=realloc(p, 80);
if (ptr != NULL)
{p = ptr;
}

在用realloc函数调整动态内存空间时,要注意不能将原来的p指针,来接收realloc(p,80)。
这是因为relloc函数不一定开辟成功新的空间而进行调整,即realloc函数的返回值可能为NULL。
那么p=NULL,本来p维护40个字节的空间。那么这样那个40个字节空间的字节就没有指针维护了。但还没有释放,可能用不到了,但可能找不到了,从而造成内存泄露

常见动态内存错误

对空指针的解引用操作

当我们用malloc函数在堆上开辟了内存空间,此时会返回一个指针,如果,我们不判断返回值的话,可能就会发生对空指针解引用的错误。

比如:

void test()
{int *p = (int *)malloc(INT_MAX*10);*p = 20;//如果p的值是NULL,就会有问题free(p);
}
  • INT_MAX 是在计算机编程中表示有符号整型(signed integer)所能存储的最大值。


如上如,p是空指针。那么就发生了对空指针的解引用操作的错误。

对动态开辟空间的越界访问

这个道理和在栈上申请空间是一样的道理。
在堆上申请空间,超过范围越界访问就会报错。

void test()
{int i = 0;int *p = (int *)malloc(10*sizeof(int));if(NULL == p){exit(-1);}for(i=0; i<=10; i++){*(p+i) = i;//当i是10的时候越界访问}free(p);
}

对非动态开辟内存使用free释放

对于在栈上开辟的空间,却用free来释放…头脑不清醒可能会用free来释放吧…

使用free释放一块动态开辟内存的一部分

例如,下面的一段代码,p没有释放掉动态内存起始位置的那块空间。
这种错误就是在写代码的过程中,起始指针跑偏了,但自己可能没有意识到。

所以,一块连续的空间必须重头释放,一次性全部释放完。

对同一块动态内存多次释放

例如下面这段代码,它释放了两次,就会出现报错。

更好的习惯是,当我们释放完一段空间后,将指针p设置为空指针。

void test()
{int *p=(int *)malloc(100);free(p);p=NULL;free(p);//此时就什么事就没有了
}

动态开辟内存忘记释放(内存泄漏)

void test5()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}int main()
{test5();while(1);return 0;
}

例如,这段代码,在主函数中,我调用test5()函数时,p指针在堆上申请了一块空间,但是函数调用完毕后,出了这个test5()函数,局部变量指针p已经销毁了。
但是,这在堆上开辟的100个空间还在占用。
并且出了这个函数,我们已经找不到这块空间的地址了,程序while(1)还在继续。
我们想用,但是不知道这块空间的起始地址,所以我们用不上。同样的,我们想要释放,我们还是释放不了。这就造成了内存泄漏
所以它只有当程序结束后,才会自动释放。

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

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

相关文章

文档智能扫描,提升无纸化办公效率

随着无纸化办公的推广和移动设备的普及&#xff0c;用户迫切需要将纸质文档快速、准确地转换成电子格式&#xff0c;以提高工作效率和信息管理的便捷性。同时&#xff0c;用户将文档扫描成电子版后&#xff0c;可以自行通过加密和访问控制提高电子文档的安全性&#xff0c;以满…

汇编的使用总结

一、汇编的组成 1、汇编指令&#xff08;指令集&#xff09; 数据处理指令: 数据搬移指令 数据移位指令 位运算指令 算术运算指令 比较指令 跳转指令 内存读写指令 状态寄存器传送指令 异常产生指令等 2、伪指令 不是汇编指令&#xff0c;但是可以起到指令的作用&#xff0c;伪…

python实现dbscan

python实现dbscan 原理 DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一个比较有代表性的基于密度的聚类算法。它将簇定义为密度相连的点的最大集合&#xff0c;能够把具有足够高密度的区域划分为簇&#xff0c;并可在噪声的空间数据库中发现任意形…

gesp(C++六级)(4)洛谷:B3874:[GESP202309 六级] 小杨的握手问题

gesp(C六级)&#xff08;4&#xff09;洛谷&#xff1a;B3874&#xff1a;[GESP202309 六级] 小杨的握手问题 题目描述 小杨的班级里共有 N N N 名同学&#xff0c;学号从 0 0 0 至 N − 1 N-1 N−1。 某节课上&#xff0c;老师安排全班同学进行一次握手游戏&#xff0c;具…

【自然语言处理(NLP)】机器翻译之数据处理(数据收集、数据清洗、数据分词、数据标注、数据划分)

文章目录 介绍机器翻译之数据处理数据收集数据清洗数据分词数据标注数据划分代码实现导包数据查看处理函数数据预处理词元化统计每句话的长度的分布情况截断或者填充文本序列将机器翻译的文本序列转换成小批量tensor加载数据试用一下 个人主页&#xff1a;道友老李 欢迎加入社区…

【物联网】ARM核常用指令(详解):数据传送、计算、位运算、比较、跳转、内存访问、CPSR/SPSR、流水线及伪指令

文章目录 指令格式&#xff08;重点&#xff09;1. 立即数2. 寄存器位移 一、数据传送指令1. MOV指令2. MVN指令3. LDR指令 二、数据计算指令1. ADD指令1. SUB指令1. MUL指令 三、位运算指令1. AND指令2. ORR指令3. EOR指令4. BIC指令 四、比较指令五、跳转指令1. B/BL指令2. l…

单链表算法实战:解锁数据结构核心谜题——链表的回文结构

题目如下&#xff1a; 解题过程如下&#xff1a; 回文结构举例&#xff1a; 回文数字&#xff1a;12521、12321、1221…… 回文字符串&#xff1a;“abcba”、“abba”…… 并不是所有的循环嵌套的时间复杂度都是O(n^2) 可以用C写C程序&#xff1a; C里可以直接使用ListNode…

计算机网络 (58)无线局域网WLAN

前言 无线局域网WLAN&#xff08;Wireless Local Area Network&#xff09;是一种利用无线通信技术将计算机设备互联起来&#xff0c;构成可以互相通信和实现资源共享的网络体系。 一、定义与特点 定义&#xff1a; WLAN通过无线信道代替有线传输介质连接两个或多个设备形成一个…

10 款《医学数据库和期刊》查阅网站

在毕业设计过程中,需要查阅到关于医学的相关文献和图片作为参考,发现下面10款非常的好用,作为分享。 1. PubMed: PubMed 搜索关键词如“lung cancer CT images”或“lung cancer CT scan”。 Radiopaedia: https://radiopaedia.org/ 这是一个放射学专业网站,有大量肺癌的CT…

OpenCV:形态学梯度

目录 简述 1. 用图像运算和腐蚀实现形态学梯度 1.1 代码示例 1.2 运行结果 2. 形态学梯度接口 2.1 参数解释 2.2 代码示例 2.3 运行结果 3. 形态学梯度与边缘检测 4. 形态学梯度的应用场景 5. 注意事项 相关阅读 OpenCV&#xff1a;图像的腐蚀与膨胀-CSDN博客 简述…

Java 大视界 -- Java 大数据在生物信息学中的应用与挑战(67)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

字符设备驱动模版-中断

字符设备驱动模版-中断 思维导图在线高清查看&#xff1a;https://www.helloimg.com/i/2025/01/27/679791b5257c0.png 修改设备树 1添加pinctrl节点 1创建对应的节点 在 iomuxc 节点的 imx6ul-evk 子节点下 2添加“fsl,pins”属性 3在“fsl,pins”属性中添加PIN配置信息 …

【SH】Windows禁用Alt+F4关机、重启、注销等功能,只保留关闭应用的功能

文章目录 组策略编辑器参考文档 组策略编辑器 亲测有效&#xff01; 1、按winr&#xff0c;输入gpedit.msc&#xff0c;回车。 2、找到》用户配置》管理模板》“开始”菜单和任务栏。 3、在右侧找到删除并阻止访问“关机”、“重新启动”、“睡眠”和“休眠”命令&#xff0c…

【深度学习】线性回归的简洁实现

线性回归的简洁实现 在过去的几年里&#xff0c;出于对深度学习强烈的兴趣&#xff0c;许多公司、学者和业余爱好者开发了各种成熟的开源框架。 这些框架可以自动化基于梯度的学习算法中重复性的工作。 目前&#xff0c;我们只会运用&#xff1a; &#xff08;1&#xff09;通…

C++中的显式构造和隐式构造

文章目录 一、概述二、显式构造函数的使用三、隐式构造函数的使用四、显式和隐式的适用场景 一、概述 在 C 中&#xff0c;构造函数可以分为 显式构造 和 隐式构造&#xff0c;它们的区别主要体现在构造函数的调用方式上。 1.显式构造&#xff08;Explicit Constructor&#…

A7. Jenkins Pipeline自动化构建过程,可灵活配置多项目、多模块服务实战

服务容器化构建的环境配置构建前需要解决什么下面我们带着问题分析构建的过程:1. 如何解决jenkins执行环境与shell脚本执行环境不一致问题?2. 构建之前动态修改项目的环境变量3. 在通过容器打包时避免不了会产生比较多的不可用的镜像资源,这些资源要是不及时删除掉时会导致服…

浅谈文献阅读(reference)对留学论文写作的重要性

很多留学生在写作留学论文时&#xff0c;拿到题目后就急于求成立马动笔写作。可是写着写着就会陷入非常迷惘的境地&#xff0c;不知道如何继续。当然这其中有很多原因&#xff0c;但其中最重要的一条&#xff0c;就是在写作英语论文之前&#xff0c;没有进行足够的知识积累&…

提升企业内部协作的在线知识库架构与实施策略

内容概要 在当前快速变化的商业环境中&#xff0c;企业对于提升内部协作效率的需求愈显迫切。在线知识库作为信息存储与共享的平台&#xff0c;成为了推动企业数字化转型的重要工具。本文将深入探讨如何有效打造与实施在线知识库&#xff0c;强调架构设计、知识资产分类管理及…

网络工程师 (3)指令系统基础

一、寻址方式 &#xff08;一&#xff09;指令寻址 顺序寻址&#xff1a;通过程序计数器&#xff08;PC&#xff09;加1&#xff0c;自动形成下一条指令的地址。这是计算机中最基本、最常用的寻址方式。 跳跃寻址&#xff1a;通过转移类指令直接或间接给出下一条指令的地址。跳…

【数据结构】_以SLTPushBack(尾插)为例理解单链表的二级指针传参

目录 1. 第一版代码 2. 第二版代码 3. 第三版代码 前文已介绍无头单向不循环链表的实现&#xff0c;详见下文&#xff1a; 【数据结构】_不带头非循环单向链表-CSDN博客 但对于部分方法如尾插、头插、任意位置前插入、任意位置前删除的相关实现&#xff0c;其形参均采用了…