动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题

本文主要是介绍动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1.malloc

2.free

3.calloc

4.realloc

在动态内存管理中的常见错误

练习:

C/C++中程序内存划分

柔性数组(了解)

动态内存管理的主要函数有malloc,calloc,realloc,free等,这些函数操作的数据都是在堆区上的,我们的内存分为了栈区,堆区,静态区,栈区主要用来存放局部变量和形式参数,堆区主要是用于动态内存开辟,静态区主要存放的是全局变量和static修饰的变量也即静态变量。

1.malloc

其头文件是stdlib.h,格式如下

用于动态开辟内存,malloc会向内存申请一块size字节大小的内存空间,并返回这块空间的地址,不过返回的地址是void*类型的,在使用的时候要根据对应的类型进行强制类型转换。

注:1.如果开辟成功,则返回一个指向开辟好空间的指针。如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。并可以使用perror("malloc")这一语句。

2.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

3.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

4.malloc只是向内存堆区申请一块空间,并不会初始化,如果我们打印,结果就是这样

这些结果就是内存中的一些随机值

2.free

malloc申请的内存空间在程序推出之前是不会还给操作系统的,需要使用free函数释放

free函数的参数就是要释放的那块空间的起始地址,你可能有疑问,要释放空间,只给了这块空间的首地址,那到底释放多大空间呢?这是因为free只能释放动态开辟的空间,且一般与malloc等函数配合使用,如果与malloc配合使用,那么malloc申请了多大空间,free就释放多大空间。

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

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

3.在使用完free函数之后应该把free置为空指针NULL

因此在动态内存管理中我们写的最多的代码就是free(p);p=NULL;

3.calloc

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

要开辟10个整形的空间,就这样写

4.realloc

realloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

ptr是要调整的内存的首地址,这个地址只能是由malloc,calloc,realloc申请的空间的首地址,如果传的ptr是NULL,那么realloc就不知道从哪里开始调整空间,他的作用就是随机申请一块空间,那么此时realloc等价于malloc

size是调整之后的新大小,比如我原来申请了四十个字节,我想要扩展到80个字节,那size的值就是80。

返回值是一个void*类型的地址,这个地址可能是ptr,也可能不是。这是因为在调整已经动态开辟的内存空间大小的时候,可能后面的空间足够,比如我申请了40个字节,我想扩展到80个字节,如果空间足够,直接开辟就完事了,然后返回ptr即可,当然也有可能在扩展的时候后面的空间已经被占用了,此时realloc就是再找一块新的内存空间,这块空间足以放80个字节,然后realloc会申请这块空间并把原来以ptr为首地址的那40个字节的空间中的内容拷贝到新开辟的空间中,然后把原来申请的那40个字节的空间释放掉。此时的返回值就是新开辟的空间的首地址。

如果realloc调整大小失败,会返回空指针NULL,也就是说realloc的返回值有三种情况,第一种是返回原来申请的那块空间的首地址,第二种是返回新开辟的空间的首地址,第三种是返回空指针,如果调整之前那块空间的地址使用一个指针变量p来存放的,那么使用realloc调整之后的地址,还能不能用这个地址存放?如果是前两种情况,当然可以使用p来存放,但是如果返回值是NULL,p就变成了空指针,以前p还维护了一块40个字节大小的空间,现在变成空指针了,就什么也不指向了,而且既然realloc调整失败了,那新的内存也肯定是没有开辟出来,这样就会导致一种情况就是:以前的那40个字节大小的空间,我们找不到了。这就是内存泄漏。为了避免内存泄漏的发生,我们可以这样写

在把realloc的返回值赋给某个指针的时候先判断一下他是不是NULL,如果不是,再赋值。

注:在使用完realloc函数之后,如果调整的空间不用了,也要及时用free函数释放掉

当然realloc也可以减少内存空间,如果是减少内存空间,就非常简单了,返回值一定是原来在堆区申请的那块空间的首地址。

在动态内存管理中的常见错误

第一,我们在使用malloc,calloc,realloc在堆区开辟空间的时候,有可能会开辟失败而返回一个空指针NULL,对他进行解引用是非法的,因此我们在动态内存管理中使用返回的地址之前应该先判断是否为NULL

第二,对动态开辟空间的越界访问。如果我们用malloc开辟了40个字节空间,但是却想访问第41个空间,就会发生错误

第三,对非动态开辟内存使用free释放,程序会直接崩溃报错

第四, 使用free释放一块动态开辟内存的一部分,举个例子

这里在for循环里面p已经被改变了,后面free(p),就只会释放掉部分开辟的内存,程序会崩溃报错。

第五,对同一块内存多次释放或者忘记释放。如果忘记释放,会造成内存泄漏。比如这样一段代码

调用test函数的时候用malloc开辟了一块空间,并用p维护这块空间,但是出了这个函数,p就销毁了,就会导致无法找到这块空间了,这就是内存泄漏,那在调用完test之后的while循环里就再也无法使用这一百个字节的空间了。

练习:

在给GetMemory传参的时候,看起来GetMemory是接受了一个地址,这是否是传址调用呢?当然不是,因为我们传的str本身就是一个指针,然后只是把str的值传给了GetMemory,因此这里采用的传参方式是传值调用,形参是实参的一份临时拷贝,对形参的操作并不会改变实参,因此str还是NULL,后面要把hell world拷贝到NULL指向的空间去,显然不正确,因此这个程序会因为对空指针进行解引用而崩溃,而且这里在GetMemory函数中申请了空间并没有释放掉,出了函数之后p销毁,导致这块内存找不到了,会造成内存泄漏。

这个print的使用其实是正确的,假如我们要打印hello world,写printf("hello world")肯定是正确的吧,那这个"hello world"实际上给printf的就是h的地址,现在我们把h的地址放在一个指针变量str里面,直接printf(str)实际上是一样的。

C/C++中程序内存划分

全局变量和静态变量放在数据段,局部变量放在栈区,常量字符串放在代码段,malloc,calloc,realloc申请的空间放在堆区。

C/C++程序内存分配的几个区域:

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

这也就是为什么使用static修饰局部变量之后会使得局部变量的生命周期变长,因为原来的局部变量在栈区上,函数调用结束之后空间被自动释放,被static修饰之后就到静态区上存放了,在程序结束时候由系统自动释放。

柔性数组(了解)

在结构体类型的声明中,如果这个结构体类型的最后一个成员变量是一个数组,且这个成员变量前面至少还有一个成员变量,那么最后面这个数组大小就可以是未知的,如果我们定义成这样

那么这个数组a就是一个柔型数组,如果编译器报错,可以写成

柔型数组的特点

结构中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

实际上要想最后一个元素的大小可变,我们完全可以使用一个指针,并不需要使用柔性数组,因此柔型数组的使用场景并不多。

这篇关于动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java 枚举的常用技巧汇总

《Java枚举的常用技巧汇总》在Java中,枚举类型是一种特殊的数据类型,允许定义一组固定的常量,默认情况下,toString方法返回枚举常量的名称,本文提供了一个完整的代码示例,展示了如何在Jav... 目录一、枚举的基本概念1. 什么是枚举?2. 基本枚举示例3. 枚举的优势二、枚举的高级用法1. 枚举

SpringBoot使用minio进行文件管理的流程步骤

《SpringBoot使用minio进行文件管理的流程步骤》MinIO是一个高性能的对象存储系统,兼容AmazonS3API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文... 目录一、拉取minio镜像二、创建配置文件和上传文件的目录三、启动容器四、浏览器登录 minio五、

IDEA常用插件之代码扫描SonarLint详解

《IDEA常用插件之代码扫描SonarLint详解》SonarLint是一款用于代码扫描的插件,可以帮助查找隐藏的bug,下载并安装插件后,右键点击项目并选择“Analyze”、“Analyzewit... 目录SonajavascriptrLint 查找隐藏的bug下载安装插件扫描代码查看结果总结Sona

IDEA中的Kafka管理神器详解

《IDEA中的Kafka管理神器详解》这款基于IDEA插件实现的Kafka管理工具,能够在本地IDE环境中直接运行,简化了设置流程,为开发者提供了更加紧密集成、高效且直观的Kafka操作体验... 目录免安装:IDEA中的Kafka管理神器!简介安装必要的插件创建 Kafka 连接第一步:创建连接第二步:选

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象