动态内存管理四大常用函数--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

相关文章

gradle第三方Jar包依赖统一管理方式

《gradle第三方Jar包依赖统一管理方式》:本文主要介绍gradle第三方Jar包依赖统一管理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录背景实现1.顶层模块build.gradle添加依赖管理插件2.顶层模块build.gradle添加所有管理依赖包

基于Python打造一个智能单词管理神器

《基于Python打造一个智能单词管理神器》这篇文章主要为大家详细介绍了如何使用Python打造一个智能单词管理神器,从查询到导出的一站式解决,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 项目概述:为什么需要这个工具2. 环境搭建与快速入门2.1 环境要求2.2 首次运行配置3. 核心功能使用指

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

Python的time模块一些常用功能(各种与时间相关的函数)

《Python的time模块一些常用功能(各种与时间相关的函数)》Python的time模块提供了各种与时间相关的函数,包括获取当前时间、处理时间间隔、执行时间测量等,:本文主要介绍Python的... 目录1. 获取当前时间2. 时间格式化3. 延时执行4. 时间戳运算5. 计算代码执行时间6. 转换为指

最详细安装 PostgreSQL方法及常见问题解决

《最详细安装PostgreSQL方法及常见问题解决》:本文主要介绍最详细安装PostgreSQL方法及常见问题解决,介绍了在Windows系统上安装PostgreSQL及Linux系统上安装Po... 目录一、在 Windows 系统上安装 PostgreSQL1. 下载 PostgreSQL 安装包2.

Python正则表达式语法及re模块中的常用函数详解

《Python正则表达式语法及re模块中的常用函数详解》这篇文章主要给大家介绍了关于Python正则表达式语法及re模块中常用函数的相关资料,正则表达式是一种强大的字符串处理工具,可以用于匹配、切分、... 目录概念、作用和步骤语法re模块中的常用函数总结 概念、作用和步骤概念: 本身也是一个字符串,其中

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

HTML5中的Microdata与历史记录管理详解

《HTML5中的Microdata与历史记录管理详解》Microdata作为HTML5新增的一个特性,它允许开发者在HTML文档中添加更多的语义信息,以便于搜索引擎和浏览器更好地理解页面内容,本文将探... 目录html5中的Mijscrodata与历史记录管理背景简介html5中的Microdata使用M

Spring 基于XML配置 bean管理 Bean-IOC的方法

《Spring基于XML配置bean管理Bean-IOC的方法》:本文主要介绍Spring基于XML配置bean管理Bean-IOC的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录一. spring学习的核心内容二. 基于 XML 配置 bean1. 通过类型来获取 bean2. 通过

python uv包管理小结

《pythonuv包管理小结》uv是一个高性能的Python包管理工具,它不仅能够高效地处理包管理和依赖解析,还提供了对Python版本管理的支持,本文主要介绍了pythonuv包管理小结,具有一... 目录安装 uv使用 uv 管理 python 版本安装指定版本的 Python查看已安装的 Python