当前位置: 首页 > news >正文

【C语言】动态内存管理

前言

为什么要有动态内存分配?

可以回想一下目前为止,我们想要向内存申请一块空间存放数据,有哪些手段呢

目前我们遇到的方式主要有两种

一、创建一个变量。比如我们存放一个整型数据,就创建一个整型变量。(也就是申请4个字节)我们创建一个变量,存放了一个数据。

int n = 2077;

二、如果我们要存放多个数据呢?这就是我们的数组,连续存储一组相同类型数据。比如下面这里申请了4个整型,也就是16个字节的空间。

int arr[4]={2,0,7,7};

但是,这两种存储方式是有缺陷的:

我们能否将n的4个字节空间扩大到10个字节、将arr的16个字节扩大到40个字节?(缩小也是同理)这是做不到的。这些空间开辟好就固定了,大小是不能变化的

那么,不能变大变小,在实际解决问题过程中就会存在缺陷

所以,C语言引入了动态内存开辟,让程序员可以自己申请和释放空间,想扩大缩小都可以,就比较灵活了。

这样的过程是动态的,顾名思义。

而实现动态内存管理的,就是四个函数:malloc 、free 、calloc、 realloc。

那么接下来就是这四个函数的介绍。

malloc函数

对于记忆一个函数名称,理解它的字母本身的含义是非常重要的:

m代表着memory,内存。alloc是allocate,分配、划拨。也就是开辟内存的意思。

头文件是stdlib.h。

参数size就是我们要开辟多大的内存块,单位是字节

返回值类型是void*,malloc帮我们开辟完空间,交给我们的方式就是把这个空间的起始地址交给我们。

注意,开辟空间的大小不是我们想多大就可以多大的。如果我们要开辟的空间过大,可能就会开辟失败。而开辟失败,这个函数就会返回空指针。

所以这个函数的返回应该有两种情况:起始地址或空指针。

int* p = (int*)malloc(10*sizeof(int));

我们无需自己计算,而是用sizeof的方式来计算10个整型的大小;同时,我们既然希望是把这块空间用于存放整型,我们就通过强制类型转换,最终用一个整型指针来接收这块空间的起始地址。

而根据我们刚才所说,malloc开辟空间可能有两种情况,所以我们要对空指针的情况进行检查

检查空指针情况
int main()
{int* p = (int*)malloc(10*sizeof(int));if(p = NULL){perror("malloc");//perror请看下文简介return 1;//返回0代表正常情况,非0值代表异常}return 0;
}
perror函数

这个函数能将错误对应的错误信息直接打印出来,原型是void perror(const char* str);它会先打印str指向的字符串,然后帮你加上":"和一个空格,再打印错误信息。

malloc申请空间的使用

言归正传,malloc申请的也是一块连续的空间,所以我们可以将其视作数组一般,在我们申请的40个字节里,存上10个整型:

int main()
{int* p = (int*)malloc(10*sizeof(int));if(p = NULL){perror("malloc");//perror请看下文简介return 1;//返回0代表正常情况,非0值代表异常}int i = 0;for(i = 0;i < 10;i++){*(p + i)=i + 1;//将1~10赋给10个整型的空间}    return 0;
}

但是我们要清楚,这样开辟的空间与数组开辟的空间的区别:
1.动态内存的大小是可以调整的。

2.开辟空间的位置不同。

看这张图,是否还记得内存分为这几个区域。

可以看到我们的数组是放在栈区的,而动态内存,也就是我们malloc free calloc realloc操作的空间是在堆区。 

最后还要注意的一点是,如果在使用malloc时,参数size的大小为0,那么malloc的行为是未定义的,取决于编译器。

free函数

上面我们做的是申请空间的动作,但其实在申请和使用完之后是需要释放空间的

在C语言中,free函数就是专门用来做动态内存的释放和回收的。

可以看到,头文件是stdlib.h,原型是void free(void* ptr);

返回类型为void,无需返回值。

参数是void*类型的指针,可以将任意类型地址的空间释放。

所以在刚才我们的代码中,只需在使用完空间后写上一句free(p);就能释放了。

free调用后是把这块空间还给操作系统了,但是p仍然存放着这块空间的地址

此时我们的p就变成了野指针p指向的空间不属于当前程序,但还是能找到这个空间

就像你将房子租给一个房客,在他退租后仍然拥有这个房子的钥匙能够访问这个空间一样,十分危险。

所以我们要收走这个“钥匙”,也就是p=NULL;

free(p);
p=NULL;
为什么要free

此时我们还能看到数组与malloc申请空间的另一个不同。

请看数组的生命周期的情况:

int main()
{int arr1[10];{int arr2[5];}//出了这里,arr2生命周期就结束了return 0;
}//出了这里arr1生命周期就结束了

注意,局部变量(数组)是进入范围创建,出了范围就自动销毁了,也就是将这块空间还给操作系统了。这种空间的生命周期的维护是自动的。但malloc申请的空间就不一样,需要手动(代码方式)释放内存。

如果不释放的话,程序结束时也会被操作系统自动回收

似乎不释放也会由操作系统“擦屁股”,但是在我们不使用这块空间后可能还有很多代码,而我们没有释放这块空间,这块空间既不用也不释放,有点浪费空间了。所以我们不用时应该释放。

而且,在后面我们想要释放的时候可能找不到这块空间了。 

使用free有两种要注意的情况:

1.如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。

2.如果参数ptr是NULL指针,则函数什么事都不做。

比如ptr是指向数组的指针时,不是动态开辟的,这是不能用free释放的。数组在栈上,而free负责的是堆区的空间。

小结

根据上面的讲解,malloc是开辟一块空间,free是释放一块空间,它俩最好成对使用

calloc函数

 calloc也是用来动态内存分配的。

返回类型仍然是void*,而有两个参数。

 

可以看到,这个函数是开辟空间并且初始化为0。 

总结就是这个函数为你开辟num个size大小的空间,把起始地址返回,同时会将内存内容初始化为0。

比如我们现在申请10个整型大小的空间(下面的malloc用于对比):

int* p1 = (int*)calloc(10,sizeof(int));
int* p2 = (int*)malloc(10*sizeof(int));

同样的,calloc开辟空间也可能失败,也会返回NULL,所以我们同样需要对NULL情况进行检查:

if(p == NULL)
{perror("calloc");return 1;
}

如果检查不是NULL,代表开辟成功,那么这块空间就可以使用。为了体现calloc开辟的特性,我们就把这块空间的值进行打印:

int i = 0;
for(i=0;i<10;i++)
{printf("%d ",p[i]);//*(p+i)
} 
free(p);//使用完也要释放
p=NULL;//别忘了,否则p变为野指针

使用完要记得释放空间和把p置为NULL。

可以看到,打印的结果就是10个0,说明calloc申请来的空间确实会被初始化为0。

malloc没有初始化的动作,率略高一些。 

realloc函数

re是再的意思,所以realloc是调整空间的意思。我们前面说了动态内存开辟来的空间可以变大变小,而变大变小依靠的就是realloc函数。

原型是void* realloc(void* ptr,size_t size);

我们再看看参数:

可以看到,ptr指向的是被malloc calloc realloc先前开辟过的空间。(所以realloc是调整)

size就是你需要调整为的空间大小。 

返回的是调整之后的内存起始位置。

现在我们可以在刚才calloc开辟空间的基础上进行调整,将其变大为20个整型的空间:

int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);}p=(int*)realloc(p, 20 * sizeof(int));//有问题free(p);p = NULL;
}

但这样写,是有问题的,我们不能直接用p来接收realloc的返回值。

在知道为什么之前,我们需要先清楚realloc到底是怎么扩大空间的:

realloc函数在申请空间时,有两种情况:

一、后面后续的空间尚未分配:那么realloc返回旧的地址(也就是参数部分传入的地址)。

二、后面后续的空间已被分配:

  1.realloc会在内存的堆区中找一块新的空间,这块空间的大小就是我们想要的新的大小(也就是realloc第二个参数size的大小);

  2.会将旧的数据,拷贝到新的空间;

  3.会释放旧的空间(不用手动free了);

  4.最后,返回新的地址。

特殊情况:也有可能扩容失败,返回NULL。

因为有这种特殊情况的存在,所以我们直接将realloc的返回值赋给p不是很好的做法,因为如果开辟失败p变为NULL,我们原本p指向空间的数据也找不到了。 

int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);}//p=(int*)realloc(p, 20 * sizeof(int));有问题int* ptr = (int*)realloc(p, 20 * sizeof(int));if (ptr != NULL){p = ptr;//如果ptr不是空指针,就再赋给p}free(p);p = NULL;
}

注意,realloc第一个参数ptr不能乱传,必须是动态开辟的起始地址,不能传动态开辟空间的中间某个位置,也不能传非动态开辟的空间地址。

realloc也可用于开辟

当我们给realloc的第一个参数ptr传的是NULL时,它的功能就和malloc一样

realloc(NULL,40) == malloc(40)

就是开辟40个字节的空间。

到此,本篇博客的内容就全部结束了,祝阅读愉快^_^

相关文章:

  • 外星人Alienware m16R1 原厂Windows11系统 oem系统
  • 16、matlab求导、求偏导、求定积分、不定积分、数值积分和数值二重积分
  • 数据挖掘 | 实验三 决策树分类算法
  • 深入理解Redis事务、事务异常、乐观锁、管道
  • 解决odbc 数据源创建之后删除失败问题
  • 抄袭瓜!斯坦福作者已删库跑路!面壁和刘知远老师的最新回应
  • 师彼长技以助己(3)逻辑思维
  • 插入排序(直接插入排序与希尔排序)----数据结构-排序①
  • Flutter 中的 ButtonBarTheme 小部件:全面指南
  • 深度学习-05-反向传播理论知识
  • (自适应手机端)响应式服装服饰外贸企业网站模板
  • 多线程新手村5--线程池
  • 全球七家半导体工厂建设受阻:英特尔、三星、台积电等面临延期挑战
  • 三十四、openlayers官网示例Dynamic clusters解析——动态的聚合图层
  • Docker安装Redis(云服务器)
  • 【Leetcode】101. 对称二叉树
  • Google 是如何开发 Web 框架的
  • 【技术性】Search知识
  • 【知识碎片】第三方登录弹窗效果
  • Angular4 模板式表单用法以及验证
  • CSS相对定位
  • Go 语言编译器的 //go: 详解
  • java8 Stream Pipelines 浅析
  • JavaScript HTML DOM
  • Magento 1.x 中文订单打印乱码
  • PHP的类修饰符与访问修饰符
  • Vue 动态创建 component
  • 闭包,sync使用细节
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 爬虫模拟登陆 SegmentFault
  • 如何合理的规划jvm性能调优
  • 手写双向链表LinkedList的几个常用功能
  • 一些css基础学习笔记
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (55)MOS管专题--->(10)MOS管的封装
  • (javascript)再说document.body.scrollTop的使用问题
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (第三期)书生大模型实战营——InternVL(冷笑话大师)部署微调实践
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)EXC_BREAKPOINT僵尸错误
  • (转)创业的注意事项
  • (转)可以带来幸福的一本书
  • .config、Kconfig、***_defconfig之间的关系和工作原理
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .gitignore文件设置了忽略但不生效