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

相关文章

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Python Counter 函数使用案例

《PythonCounter函数使用案例》Counter是collections模块中的一个类,专门用于对可迭代对象中的元素进行计数,接下来通过本文给大家介绍PythonCounter函数使用案例... 目录一、Counter函数概述二、基本使用案例(一)列表元素计数(二)字符串字符计数(三)元组计数三、C

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优