【数据结构】动态内存管理函数

动态内存管理

  • 为什么存在动态内存管理
  • 动态内存函数的介绍
    • 🎊malloc
    • 补充:perror函数
    • 🎊free
    • 🎊calloc
    • 🎊realloc
  • 常见动态内存错误
    • 对空指针的解引用操作
    • 对动态开辟空间的越界访问
    • 对非动态开辟内存使用free释放
    • 使用free释放一块动态开辟内存的一部分
    • 对同一块动态内存多次释放
    • 动态开辟内存忘记释放(内存泄漏)

为什么存在动态内存管理

在此之前,我们开辟内存空间有两种方式。一种是创建一个已知类型的变量。
比如说:

int a=10;  //在栈空间上开辟4个字节

向系统申请了4个字节的内存空间。(对于 int型,4个字节它是固定的。)
还有一种是,创建一个数组。
比如说:

int arr[10]; //在栈空间上开辟40个字节的连续空间。

向系统申请了40个字节的内存空间。当这个数组开辟好了空间,没有办法改变它的大小。
对于数组的创建,它的内存开辟方式是比较死板的。

int arr1[10];int arr2[100]

我们创建数组时,在一开始时就会指定数组的大小。arr1的内存空间为40个字节,可以存放10个整型元素。arr2的内存空间为400个字节,可以存放100个整型元素。

但有可能我们在使用数组arr1的时候,需要存放11个数组元素,而没有办法把它边长
我们可能为了尽可能满足很多情况,而创建一个很大的数组arr2,但在实际使用过程中,我们可能只会存放20个元素,而导致了内存空间的浪费

所以这样的内存开辟方式它是固定的,是不够灵活的。 不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才能知道,那么数组在编译时开辟空间的方式就不能满足了。 所以,我们需要学会开辟动态内存

动态内存函数的介绍

malloc
free
calloc
realloc

🎊malloc

malloc函数的原型:

void* malloc(size_t size);

malloc声明在stdlib.h头文件中。
功能:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回指向这块空间的指针。
  • 如果开辟失败,则返回一个NULL指针。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

因此,malloc函数的返回值一定要做检查。

举个例子:
原来,我们用数组栈区开辟内存空间:

现在,我们在堆区动态开辟同样大小的内存空间:

根据malloc函数的原型,我们需要传递一个参数,以字节为单位的内存。

malloc(40)//即开辟了40个字节的内存空间

然后,我们需要一个指针p来指向这块儿开辟好的连续的 内存空间
但由于 malloc函数的返回值为 void*,即无类型指针,所以我们需要先进行强制转换,将无类型指针转换为整型指针
因此,

int* p=(int*)malloc(40);

此时我们,开辟的空间在内存中的堆区的空间,但是指向这块空间的指针是放在栈中的,也就是上面例子中的p指针。
如下图所示。
在这里插入图片描述

但是,正如我们上面所提到的,我们只是用malloc函数向内存申请开辟40个自己的连续空间,不一定开辟成功。所以我们需要利用指针p进行进一步检验

若开辟成功,进行访问:

malloc函数 申请的内存空间,但程序退出时,不会主动释放的,需要使用free函数来释放。

补充:perror函数

perror函数(忘的打印输出函数)

来自这篇博客:C语言perror函数详解

🎊free

C语言提供free函数,专门用来做动态内存释放和回收的。
free函数原型:

void free(void *ptr);

free函数也是声明在头文件<stdlib.h>中的。
free函数用来释放动态开辟的内存。

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,则函数什么事都不做。

🎊calloc

C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配。
calloc函数的原型:

void* calloc(size_t num,size_t size);

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

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));//开辟10个大小为sizeof(int),即4个字节 的空间//判断是否开辟成功if (p == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++)printf("%d\n", *(p + i));return 0;
}

在这里插入图片描述

🎊realloc

  • realloc函数的出现让动态内存管理更加灵活
    有时候我们发现之前申请的内存过小了,有时候我们发现我们申请的内存过大了,所以,在一些时候,我们需要对内存的大小做灵活的调整。那么realloc函数就可以做到对动态开辟内存大小的调整
    realloc函数的原型如下:
void* realloc(void* ptr,size_t size);

其中,ptr是要调整的内存的地址
size是调整之后的新的内存的大小。
返回值是调整之后内存的起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间。
举个例子:
我们首先使用malloc函数开辟40个字节的空间:

//malloc函数申请空间
int* p = (int*)malloc(40);
if (p == NULL)
{perror("malloc");return 1;
}
//初始化
int i = 0;
for (i = 0; i < 10; i++)*(p + i) = i;

我们想要将这个空间扩大,扩大为80个字节的空间。
于是我们使用realloc函数进行调整。但是内存空间的变化可能有不同的情况。
情况一,后面有足够的空间。
即这样的情形:
要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。

情况二,后面没有足够的空间:
那么就在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

//增加一些空间
int *ptr=realloc(p, 80);
if (ptr != NULL)
{p = ptr;
}

在用realloc函数调整动态内存空间时,要注意不能将原来的p指针,来接收realloc(p,80)。
这是因为relloc函数不一定开辟成功新的空间而进行调整,即realloc函数的返回值可能为NULL。
那么p=NULL,本来p维护40个字节的空间。那么这样那个40个字节空间的字节就没有指针维护了。但还没有释放,可能用不到了,但可能找不到了,从而造成内存泄露

常见动态内存错误

对空指针的解引用操作

当我们用malloc函数在堆上开辟了内存空间,此时会返回一个指针,如果,我们不判断返回值的话,可能就会发生对空指针解引用的错误。

比如:

void test()
{int *p = (int *)malloc(INT_MAX*10);*p = 20;//如果p的值是NULL,就会有问题free(p);
}
  • INT_MAX 是在计算机编程中表示有符号整型(signed integer)所能存储的最大值。


如上如,p是空指针。那么就发生了对空指针的解引用操作的错误。

对动态开辟空间的越界访问

这个道理和在栈上申请空间是一样的道理。
在堆上申请空间,超过范围越界访问就会报错。

void test()
{int i = 0;int *p = (int *)malloc(10*sizeof(int));if(NULL == p){exit(-1);}for(i=0; i<=10; i++){*(p+i) = i;//当i是10的时候越界访问}free(p);
}

对非动态开辟内存使用free释放

对于在栈上开辟的空间,却用free来释放…头脑不清醒可能会用free来释放吧…

使用free释放一块动态开辟内存的一部分

例如,下面的一段代码,p没有释放掉动态内存起始位置的那块空间。
这种错误就是在写代码的过程中,起始指针跑偏了,但自己可能没有意识到。

所以,一块连续的空间必须重头释放,一次性全部释放完。

对同一块动态内存多次释放

例如下面这段代码,它释放了两次,就会出现报错。

更好的习惯是,当我们释放完一段空间后,将指针p设置为空指针。

void test()
{int *p=(int *)malloc(100);free(p);p=NULL;free(p);//此时就什么事就没有了
}

动态开辟内存忘记释放(内存泄漏)

void test5()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}int main()
{test5();while(1);return 0;
}

例如,这段代码,在主函数中,我调用test5()函数时,p指针在堆上申请了一块空间,但是函数调用完毕后,出了这个test5()函数,局部变量指针p已经销毁了。
但是,这在堆上开辟的100个空间还在占用。
并且出了这个函数,我们已经找不到这块空间的地址了,程序while(1)还在继续。
我们想用,但是不知道这块空间的起始地址,所以我们用不上。同样的,我们想要释放,我们还是释放不了。这就造成了内存泄漏
所以它只有当程序结束后,才会自动释放。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/68441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在FreeBSD下安装Ollama并体验DeepSeek r1大模型

在FreeBSD下安装Ollama并体验DeepSeek r1大模型 在FreeBSD下安装Ollama 直接使用pkg安装即可&#xff1a; sudo pkg install ollama 安装完成后&#xff0c;提示&#xff1a; You installed ollama: the AI model runner. To run ollama, plese open 2 terminals. 1. In t…

C++类和对象下详细指南

C类和对象下详细指南 1. 初始化列表与构造函数 1.1 初始化列表概述 初始化列表在C中用于初始化对象的成员变量&#xff0c;特别是当你需要在对象构造时就明确成员变量的值时。通过初始化列表&#xff0c;成员变量的初始化可以在进入构造函数体之前完成。这不仅可以提升性能&…

文档智能扫描,提升无纸化办公效率

随着无纸化办公的推广和移动设备的普及&#xff0c;用户迫切需要将纸质文档快速、准确地转换成电子格式&#xff0c;以提高工作效率和信息管理的便捷性。同时&#xff0c;用户将文档扫描成电子版后&#xff0c;可以自行通过加密和访问控制提高电子文档的安全性&#xff0c;以满…

汇编的使用总结

一、汇编的组成 1、汇编指令&#xff08;指令集&#xff09; 数据处理指令: 数据搬移指令 数据移位指令 位运算指令 算术运算指令 比较指令 跳转指令 内存读写指令 状态寄存器传送指令 异常产生指令等 2、伪指令 不是汇编指令&#xff0c;但是可以起到指令的作用&#xff0c;伪…

【玩转全栈】----Django模板的继承

先赞后看&#xff0c;养成习惯&#xff01;&#xff01;&#xff01; 目录 模板继承的好处 模板继承的语法规则 更新代码 上文中的部门管理页面&#xff1a; 【玩转全栈】----Django制作部门管理页面-CSDN博客 大家会发现&#xff0c;由于定义了多个html文件&#xff0c;多个ht…

nosql mysql的区别

NoSQL 和 MySQL 是两种不同类型的数据库管理系统&#xff0c;它们在设计理念、数据模型、可扩展性和应用场景等方面有着本质的区别。 NoSQL 数据库 特点: 灵活的数据模型: NoSQL 数据库通常没有固定的表结构&#xff0c;可以很容易地存储不同结构的文档或键值对。水平扩展: …

python实现dbscan

python实现dbscan 原理 DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一个比较有代表性的基于密度的聚类算法。它将簇定义为密度相连的点的最大集合&#xff0c;能够把具有足够高密度的区域划分为簇&#xff0c;并可在噪声的空间数据库中发现任意形…

Lustre Core 语法 - 比较表达式

概述 Lustre v6 中的 Lustre Core 部分支持的表达式种类中&#xff0c;支持比较表达式。相关的表达式包括 , <>, <, >, <, >。 相应的文法定义为 Expression :: Expression Expression | Expression <> Expression | Expression < Expression |…

gesp(C++六级)(4)洛谷:B3874:[GESP202309 六级] 小杨的握手问题

gesp(C六级)&#xff08;4&#xff09;洛谷&#xff1a;B3874&#xff1a;[GESP202309 六级] 小杨的握手问题 题目描述 小杨的班级里共有 N N N 名同学&#xff0c;学号从 0 0 0 至 N − 1 N-1 N−1。 某节课上&#xff0c;老师安排全班同学进行一次握手游戏&#xff0c;具…

【自然语言处理(NLP)】机器翻译之数据处理(数据收集、数据清洗、数据分词、数据标注、数据划分)

文章目录 介绍机器翻译之数据处理数据收集数据清洗数据分词数据标注数据划分代码实现导包数据查看处理函数数据预处理词元化统计每句话的长度的分布情况截断或者填充文本序列将机器翻译的文本序列转换成小批量tensor加载数据试用一下 个人主页&#xff1a;道友老李 欢迎加入社区…

【物联网】ARM核常用指令(详解):数据传送、计算、位运算、比较、跳转、内存访问、CPSR/SPSR、流水线及伪指令

文章目录 指令格式&#xff08;重点&#xff09;1. 立即数2. 寄存器位移 一、数据传送指令1. MOV指令2. MVN指令3. LDR指令 二、数据计算指令1. ADD指令1. SUB指令1. MUL指令 三、位运算指令1. AND指令2. ORR指令3. EOR指令4. BIC指令 四、比较指令五、跳转指令1. B/BL指令2. l…

过年之无用知识研究:std::is_assignable到底怎么个事?

下面是gcc的std::is_assignable相关源码&#xff1a;template<typename _Tp, typename _Up>class __is_assignable_helper{template< typename _Tp1, typename _Up1,typename decltype(declval<_Tp1>() declval<_Up1>()) //注意这行 >static true_ty…

单链表算法实战:解锁数据结构核心谜题——链表的回文结构

题目如下&#xff1a; 解题过程如下&#xff1a; 回文结构举例&#xff1a; 回文数字&#xff1a;12521、12321、1221…… 回文字符串&#xff1a;“abcba”、“abba”…… 并不是所有的循环嵌套的时间复杂度都是O(n^2) 可以用C写C程序&#xff1a; C里可以直接使用ListNode…

C++ 包装器与绑定器的应用之回调函数的实现

回调函数的实现 在消息队列和网络库的框架中&#xff0c;当接收到消息&#xff08;报文&#xff09;时&#xff0c;回调用户自定义的函数对象&#xff0c;把消息&#xff08;报文&#xff09;参数传给它&#xff0c;由它决定如何处理。 queue参考文章:C queue(STL queue&…

除了layui.js还有什么比较好的纯JS组件WEB UI?在谷歌浏览上显示

以下是一些比较好的纯JS组件WEB UI&#xff0c;可以在谷歌浏览器上良好显示&#xff1a; 1. Sencha 特点&#xff1a;提供超过140个高性能UI组件&#xff0c;用于构建现代应用程序。支持与Angular和React集成&#xff0c;提供企业级网格解决方案。 适用场景&#xff1a;适用于…

计算机网络 (58)无线局域网WLAN

前言 无线局域网WLAN&#xff08;Wireless Local Area Network&#xff09;是一种利用无线通信技术将计算机设备互联起来&#xff0c;构成可以互相通信和实现资源共享的网络体系。 一、定义与特点 定义&#xff1a; WLAN通过无线信道代替有线传输介质连接两个或多个设备形成一个…

AIP-132 标准方法:List

编号132原文链接AIP-132: Standard methods: List状态批准创建日期2019-01-21更新日期2022-06-02 在许多API中&#xff0c;通常会向集合URI&#xff08;例如 /v1/publishers/1/books &#xff09;发出GET请求&#xff0c;获取集合中资源的列表。 面向资源设计&#xff08;AIP…

Vue.js组件开发-Vue实现上传word模版打印设置自定义样式和布局

要使用 Vue 实现上传 Word 模板、打印并设置自定义样式和布局&#xff0c;可以借助一些工具和库来完成这个任务。 实现步骤 创建 Vue 项目&#xff1a;使用 Vue CLI 创建一个新的 Vue 项目。安装依赖&#xff1a;安装 docx-templates 库来处理 Word 模板&#xff0c;file-sav…

算法随笔_28:最大宽度坡_方法2

上一篇:算法随笔_27:最大宽度坡-CSDN博客 题目描述如下: 给定一个整数数组 nums&#xff0c;坡是元组 (i, j)&#xff0c;其中 i < j 且 nums[i] < nums[j]。这样的坡的宽度为 j - i。 找出 nums 中的坡的最大宽度&#xff0c;如果不存在&#xff0c;返回 0 。 示例 …

C++中函数返回值当引用

文章目录 一、概述二、返回值当引用的基本语法三、返回局部变量的引用四、返回引用的常见用途五、返回右值引用六、总结 一、概述 在 C 中&#xff0c;函数返回值当引用&#xff08;即返回引用&#xff09;是一个常见的编程技巧。它可以让你返回一个函数内部的局部变量或对象的…