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

自由的程序员应该学会自由地控制空间-----动态内存管理

动态内存管理那些事儿~~~

  • 动态内存存在的意义
  • 动态内存函数介绍
    • 1.malloc&&free
    • 2,calloc&&free
    • 3.realloc&&free
  • 柔性数组
  • 一点建议

动态内存存在的意义

老话有云:存在即是合理,如果动态内存毫无意义,那么它早就被我们程序员所丢弃了!
事实上,随着我们学习的深入,我们会发现很多情况下,我们不应该仅仅局限于会使用char arr【】这种级别的创建数组,不是说这样写不好,而是说只有这一种写法不足以满足我们,比如我的电脑现在内存很紧张,但是我还必须创建一个很大的数组,我没有办法一下子全部给它创建好啊!没有那个能力啊!我们是不是就可以选择慢慢来,先创建一部分,等后面有空间了继续创建,但是我们静态的数组能够实现增加数组的内存这项功能嘛?显而易见是不可以的!这时候我们的动态内存粉墨登场,我们可以极大程序地决定我们所需要的空间大小!

动态内存函数介绍

先说一句题外话吧,讲一讲阿涛关于内存的一些小回忆,小时候家里人是比较反对我玩电脑的,然后每每当他们快要走到我的面前时我都会迅速按下ctrl alt .这三个键,这时候就会显示出任务管理器:
在这里插入图片描述
点进去就会出现这样的一个界面,所以我见到内存的次数应该不会少,我当时就发现了只要那个内存占用很大的时候电脑就会变得很卡,这也就是我对于内存的一个直观感受吧!

1.malloc&&free

在这里插入图片描述
下面我们来正是学习动态内存函数:
首先我们来学习比较容易上手的malloc函数,malloc函数的参数是一个无符号整型,这里我们需要给它传的参数是我们需要开辟的动态内存的总大小,当然了是要以字节为单位的。
这里注意一下我们要学习的三个动态内存函数的返回类型全是void类型,这是为什么呢?因为设计这些函数的人不是正在敲代码的你,他是不会预知你想要创建的内存是存储一些什么类型的数据的,而我们恰好又知道,void是非常兼容的一个类型,无论是什么类型的指针都是可以往里面放的,所以这里使用void就会显得格外的智慧,但是我们又知道对于void类型的数据是不可以进行解引用,加减整数的操作的,因此我们作为使用者还应该对于我们开辟出来的空间进行一次强制类型转化
在这里插入图片描述可以看到我们没有使用常规的创建数组然后存放数据,而是使用了动态内存,自己给自己创建空间!

	int* ptr = (int*)malloc(40);//malloc返回的是我们创建的空间的首地址(void*),
	//我们要对其进行强制类型转化!
	for (int i = 0;i < 10;i++)
	{
		*(ptr + i) = i;//这里就是把ptr看成了是数组名,当成首元素地址来处理,我们之前
		//也说过数据的类型决定了访问元素时能够访问的内存的大小,所以这里就当成数组使用
	}
	for (int i = 0;i < 10;i++)
	{
		printf("%d ",ptr[i]);
	}
	free(ptr);//这是什么?
	ptr = NULL;//这又是什么?
	return 0;

在上面我给兄弟们抛出了两个问题,这两个问题基本上是一讲就会,但是实际使用的时候一用就忘!
在这里插入图片描述
这里跟兄弟们科普一下小知识:
首先说好,我只是列举了各个区域上面可能出现的类型,并不是全部哦!
在栈区上面创建的变量,比如函数吧,等到我们调用完函数,那么函数里面创建的变量的内容就会自动还给操作系统,但是对于动态内存函数不一样,它是在堆区上面创建的,除非程序结束或者是我们手动给它free掉,否则的话,它是不会把内存还给操作系统用的,就硬占着!
那肯定就有好奇的小宝宝会有疑惑了,我们电脑现在的内存都是16G32G起步的,还会怕这三瓜两枣?我告诉你还真的挺怕的!
这是我们运行之前的内存图例:
在这里插入图片描述这是我们循环开辟动态内存并且不释放的效果图:
在这里插入图片描述
我们可以看到中间有一块很明显的上升趋势,这里它爬坡只爬了一会的原因是,我们的VS2019不是那么死板的机器,我让它无限开辟它就照做,事实上它自己也是有保护机制的,但是我们程序员可不敢赌每一个IDE都这么人性化啊,万一你一不小心写出的一个BUG导致了工作的电脑宕机了,那分分钟我们就是损失了上千万怎么办?
那肯定有机灵的小朋友抓到了我上面讲解的漏洞了,阿涛啊,那你不是说程序只要结束了,堆区上面的内存还是会还给操作系统的嘛?那我们为什么还是要手动清理呢?
请问一下,这个程序结束不管是你手动结束还是电脑直接强制你结束程序,那都不是一种健康的代码啊,证明你写的的代码是有问题的,不然的话为什么不可以运行呢?这就相当于,你的电脑出现问题了,你只要关机了就看不到问题了,但是问题是只要你敢开机,那个问题就会一直存在啊!问题得不到解决,说什么都没有用啊!
好了这里注意一下:
***free()***函数的参数是我们动态开辟的内存的首地址,而且我们只可以free动态开辟的内存,不然就会这样:
在这里插入图片描述所以,如果我们需要移动这个起始位置的地址之前,最好保存一份,留一条后路,指引我们回家!
好了现在我们的内存也还给操作系统了,就相当于我们已经退租了,但是我们手上是不是还有一把出租屋的钥匙:ptr啊?之前我们可不就是用它来接收动态开辟内存的首地址的吗?现在我们内存已经还给了操作系统,但是通过这把钥匙,我们依旧可以找到之前开辟的那块内存,这是相当不合理的!
既然我们已经把这块内存还给了操作系统,如果我们继续访问,有可能我们还是可以读到上一次我们写进去的数据,但是有没有这样一种可能,操作系统已经对这块内存进行了重新分配任务?她已经名花有主啦!这种时候你还想着访问别人的内存是不是就是属于非法访问了?是不是有罪?
所以我们最后再把起始位置的地址给赋值为空指针,再把钥匙还给房东,从此两清!

2,calloc&&free

讲完了malloc,接下来我们学习calloc和realloc就会显得异常轻松!
在这里插入图片描述
calloc的参数似乎和我们刚才讲的malloc不一样哦!
calloc函数的第一个参数似乎是数目,第二个函数好像是大小!那我们就大胆猜测一波:
第一个参数表示我们开辟的这块内存中的元素个数,第二个参数对应了每个参数的大小!
在这里插入图片描述
很成功的一波猜测!
那不免就会有些人犯嘀咕了,既然malloc和calloc这么高度相似,那么他们之间是不是还是会有一些轻微的区别呢?
事实上calloc会在创建好空间的时候就帮助你把空间里面全部赋值为零!
malloc理论上是不会多此一举的,但是保不齐有的编译器就是这么智能,也会这么做!
free以及空指针不可以忘记啊!

3.realloc&&free

就目前我们讲的那个函数好像并没有很好的体现出我们的动态二字啊!现在看来好像和静态的开辟并没有什么区别啊!我知道你很急,但是请你先别急,动态的实现就是靠着我们这个函数:realloc
在这里插入图片描述
realloc函数需要你把动态开辟的内存的首地址传为第一个参数,而第二个参数就是你希望这块内存变为多大的内存,以字节为单位的!
你觉得内存嫌小,就往大了加,你觉得内存太大了会浪费,就往小了变!

在这里插入图片描述
这里我们可以看到内存,本来我们只是开辟了四十个字节,现在我们继续往下面走一步你们好好看着啊!
在这里插入图片描述
看!我们是不是可以使用最后我们加上的那四个字节的空间了?
当然了如果是变大会有一点点需要注意的地方:

这么一看是不是没有问题?
在这里插入图片描述现在呢?嗯?为什么两次的地址不一样呢?
在这里插入图片描述
因为对于我们追加的那段内存
在这里插入图片描述
是有可能遇到这种情况的,如果后面的内存不够我们追加了,难道我们还能够当土匪嘛?
就直接落草为寇了?我们法治社会可不兴这套!
那么现在我们就会另外找到一块能够存放我们追加完的内存,然后把之前的内存中的数据拷贝进去,之后再把原先的那块内存给销毁,这样子就完成了我们的追加工作!
还是那句话,不可以忘记free以及置为空指针!

柔性数组

讲完了这个动态内存函数,现在我们来讲一下这个的一个小应用吧!
柔性数组这个概念大家应该不太熟悉,毕竟学校的课程不可能详细到应有尽有!

struct CYT
{
	int a;
	char b;
	int a[];//int a[0];
};

具体来说柔性数组就是这么一个写法:
在这里插入图片描述
我们可以看到计算这个结构体大小i的时候是没有把最后的柔性数组算进去的,在柔性数组之前至少也应该有一个变量,这是我们的规定!好了现在我们还想要使用这个柔性数组应当进行怎么样的操作呢?拭目以待!
在这里插入图片描述
看到没,是不是这样子的数组我们也实现了他的作用?是不是达到了我们想要的同台的效果?我们想要多大的内存就能有多大的内存!
可是好像还是少了点什么东西啊!
兄弟们切记,只要是开辟了动态内存就要记得归还!有借有还再借不难啊!

	cyt = (struct CYT*)malloc(sizeof(struct CYT) + sizeof(int) * 10);

我猜测啊,一定会有兄弟对这里不理解,为什么我们不可以单独开辟数组的内存呢?就类似于这样?
在这里插入图片描述

就结果来看,我们都是圆满完成了任务!

		free(cyt);
		free(cyt->c);
		cyt->c = NULL;
		cyt = NULL;

当然了最后的交接工作可不能忘记啊!
如果选择我们后面的这个方法,有可能我们忘记free空间,有可能我们只记得free其中的一个,如果我们使用第二种方法,那么我们是开辟的两段空间:
在这里插入图片描述
那么中间的空间碎片对于我们来说,是不太方便使用的,所以倒不如我们一次性使用到位!

一点建议

最后再给兄弟们一点建议:
如果我们试图开辟动态内存,但是由于种种原因我们开辟失败了那么,从定义上我们可以知道,那些个函数都会返回一个空指针,但是如果我们还不明就里地进行一顿操作那是不是不太合适了?因此我们不妨加上这么一段判断步骤:

	cyt = (struct CYT*)malloc(sizeof(struct CYT));
	if (cyt == NULL)
	{
		perror("malloc:");
		return 1;
	}

这样子如果我们开辟失败了,就会报个错然后直接返回了,这样子就可以即使止损,避免了许多许多的问题吧!

好的,那么讲到这里我们关于动态内存的知识也差不多了,希望我的这篇博客能够对兄弟们有些许的帮助!
百年大道,你我共勉!

相关文章:

  • [架构之路-51]:架构师 - 用系统化、结构化思维解决复杂难搞的软件故障问题 - 马克思主义哲学在软件系统中的应用
  • 【面试题】JavaScript数组切片方法有哪些?
  • 【PyTorch深度学习项目实战100例】—— 基于CNN卷积神经网络实现中文手写汉字识别 | 第60例
  • HarmonyOS系统中内核实现UART串口通信方法
  • Selenium4.0+Python三种元素等待方式介绍 及 元素等待封装
  • django梳理
  • 嵌入式软件调试的发展历程
  • PT_连续型随机变量/分布函数/概率密度
  • Python告别pip手动安装模块,实现全自动安装第三方库,彻底解放你的双手
  • 文件目录操作——Linux命令核心
  • Taichi 加速 Python 中图像处理
  • Vue--》MVVM模型在Vue中的使用
  • 迷宫求解(云南大学)
  • 【夜读】坚持这5个习惯,遇见更优秀的自己
  • 算法与数据结构【30天】集训营——平衡二叉树的LL、RR、LR、RL调整的简单快速方法(16)
  • Fundebug计费标准解释:事件数是如何定义的?
  • GraphQL学习过程应该是这样的
  • Python 基础起步 (十) 什么叫函数?
  • React系列之 Redux 架构模式
  • SQL 难点解决:记录的引用
  • Transformer-XL: Unleashing the Potential of Attention Models
  • 笨办法学C 练习34:动态数组
  • 创建一种深思熟虑的文化
  • 前端性能优化——回流与重绘
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 鱼骨图 - 如何绘制?
  • linux 淘宝开源监控工具tsar
  • 阿里云重庆大学大数据训练营落地分享
  • ​人工智能书单(数学基础篇)
  • #mysql 8.0 踩坑日记
  • #微信小程序(布局、渲染层基础知识)
  • (+4)2.2UML建模图
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (javascript)再说document.body.scrollTop的使用问题
  • (Ruby)Ubuntu12.04安装Rails环境
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .NET 反射 Reflect
  • .net 逐行读取大文本文件_如何使用 Java 灵活读取 Excel 内容 ?
  • .netcore如何运行环境安装到Linux服务器
  • .net专家(高海东的专栏)
  • .skip() 和 .only() 的使用
  • @RunWith注解作用
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(朱雀组)
  • [BZOJ4566][HAOI2016]找相同字符(SAM)
  • [C语言]——内存函数
  • [docker] Docker容器服务更新与发现之consul
  • [ios] IOS文件操作的两种方式:NSFileManager操作和流操作【转】
  • [LeetCode]-225. 用队列实现栈-232. 用栈实现队列
  • [Oh My C++ Diary]函数重载
  • [Oh My C++ Diary]头文件使用之math.h与cmath
  • [Oracle][Metadata]如何查找与某一个功能相关的数据字典名
  • [PAT] 1041 Be Unique (20 分)Java
  • [POI2009]WIE-Hexer