动态内存分配及管理——C语言

目录

一、为什么存在动态内存分配

二、动态内存函数介绍

2.1 malloc

2.2 free

2.3 calloc

2.4 realloc

三、常见的动态内存错误

3.1 对NULL指针的解引用操作

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

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

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

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

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


        今天,博主给大家带来的是动态内存分配的学习和讲解。在之前,我们学习了通讯录,文章中利用到一些动态内存分配的一些知识,有些可能大家会看不懂,那么相信通过今天的这篇文章,大家的问题就会迎刃而解。本篇,我们将从“为什么存在内存分配”,“动态内存函数介绍”,以及“常见的动态内存错误”三个板块来为大家一 一解答。

一、为什么存在动态内存分配

在之前,我们学过的内存开辟有哪些呢?比如,创建一个变量,或者创建一个数组。

    int a = 10;//在栈空间开辟四个字节char arr[10] = { 0 };//在栈空间开辟十个字节的连续空间

上面两种开辟方式是我们常用的开辟内存的方式,但是这两种开辟内存空间的方式有两个特点:

① 空间开辟大小是固定的

② 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是由于空间的需求,有时候我们需要空间的大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足我们的需求了,这时就要试试动态内存开辟的方式了。

二、动态内存函数介绍

在学习动态内存函数之前,我们需要知道动态内存开辟的空间是放在堆区的,如上图所示,局部变量和形式参数占用的空间是在栈区的,全局变量以及静态变量开辟的空间是在静态区的。 

2.1 malloc

C语言提供了一个动态内存开辟函数

void* malloc  (   size_t    size   ) ;
这个函数向内存申请一块 连续可用 的空间,并返回指向这块空间的指针。
① 如果开辟成功,则返回一个指向开辟好空间的指针。
② 如果开辟失败,则返回一个NULL指针,因此 malloc 的返回值一定要做检查。
③ 返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
④ 如果参数 size 为 0 malloc 的行为是标准是未定义的,取决于编译器。
举个代码例子来解释吧!
int main()
{int* p = (int*)malloc(40);//开辟40个字节的空间if (p == NULL){perror("malloc");return 1;}//开辟成功for (int i = 0; i < 10; i++){printf("%d\n", *(p + i));}return 0;
}
因为返回的类型是void*,所以我们要根据自己的需求来进行强制类型转换,其次,我们需要判断是否开辟成功,如果返回值为NULL指针,那么就结束了,反之则是开辟成功。然后我们打印一下看看开辟成功的空间里面是什么。

此时我们发现开辟的空间里面存的是一堆没见过的随机数数,其实是malloc函数申请的空间,在申请成功后,直接返回这片空间的起始地址,不会初始化空间的内容。 

2.2 free

C 语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的,函数原型如下:
void free void*   ptr  ) ;
上面我们学习了malloc函数,我们发现,malloc只负责申请空间,那么申请的这个空间当我们使用完之后会怎么样呢?其实这块空间并不会主动的还给操作系统,除非程序结束,否则这块空间将会一直存在堆区。这个时候就需要另一个内存函数了。
总的来说: malloc申请的内存空间,当程序退出时,还给操作系统,当程序不退出时,动态申请的内存不会主动释放。需要free函数来释放空间。
具体怎么使用呢,直接把我们动态开辟的空间的起始地址传入free函数中即可,代码如下图所示:
int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//开辟成功free(p); //释放开辟的空间p = NULL;  //置空return 0;
}

注意:p本来指向的空间被释放后,p就变成野指针了,比较危险,这时候我们需要主动将p置为空指针。

free只能释放动态开辟的空间,不能释放静态区或者栈区开辟的空间(标准未定义) 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

② 如果参数 ptr NULL 指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头文件中

2.3 calloc

C 语言还提供了一个函数叫 calloc calloc 函数也用来动态内存分配。原型如下:
void*   calloc size_t   num  ,   size_t   size  ) ;
① 函数的功能是为 num 个大小为 size 的元素开辟一块空间 ,并且把空间的每个字节初始化为 0
② 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0
举个例子吧:
int main()
{int* p = (int*)calloc(10, sizeof(int));//开辟十个连续的sizeof(int)大小的空间if (p == NULL){perror("calloc");return 1;}//打印数据int i = 0;for (i = 0; i < 10; i++){printf("%d ", p[i]);}//释放free(p);p = NULL;return 0;
}

当我们打印完之后,发现calloc会把每个字节初始化为0。

总的来说:calloc函数和malloc函数很相似,功能也是一样,唯一不同的就是,会把开辟的每个字节都初始化为0。

        所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc 函数来完成任务。

2.4 realloc

realloc 函数的出现让动态内存管理更加灵活。
        有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void*   realloc  void*   ptr  ,   size_t   size  ) ;
① ptr 是要调整的内存地址(也就是被调整空间的起始地址,这块空间之前已经开辟好了)
如果ptr为空指针,那么它的功能和malloc就是一样的,开辟一个新的空间。
② size 调整之后新大小 (需要调整的新的空间的大小)
③ 返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 的空间。
④ realloc 在调整内存空间的是存在两种情况:
情况1 :原有空间之后有足够大的空间
情况2: 原有空间之后没有足够大的空间
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,同时把原来空间的内容拷贝过来,然后自动释放原来的内存空间,这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些,我们这里用代码来举例子吧:
int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//初始化为1~10int i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}//增加一些空间int* ptr = (int*)realloc(p, 80);if (ptr != NULL)  //开辟成功{p = ptr;ptr = NULL;}else  //开辟失败{perror("realloc");return 1;}//开辟成功,打印数据for (i = 0; i < 20; i++){printf("%d ", p[i]);}free(p);  //释放空间p = NULL;  //置空return 0;
}

注意:考虑到可能开辟失败,所以我们需要先进行判断一下,如果开辟成功,则将返回的指针赋值给p来维护。

当我们开辟完之后,打印一下看看效果。

当我们使用完动态开辟的内存之后,仍然需要手动去释放内存空间。

 当然,上面情况只是减少增加空间,如果要减少空间就比较简单了,直接在原来的基础上减少,地址返回的也是原来的地址。

好了,到这里动态内存管理的基本知识就介绍清楚了,实际上把这四个内存函数了解清楚,基本上对动态内存的知识点也就基本掌握了。接下来,我们来了解一下动态内存管理常见的内存错误,通过解释这些错误,来更清楚更深入的了解动态内存管理。

三、常见的动态内存错误

3.1 对NULL指针的解引用操作

当我们动态内存开辟的时候,会存在开辟失败的情况,此时返回的就是空指针。

如下代码就是一个典型的例子:

void test()
{int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}

此时动态内存开辟可能失败了,就导致返回的指针为空指针,也就是p为空指针,如果再对p这个空指针进行解引用操作,那么就会报错 。为了解决这种问题,我们在平时写代码的时候,为了避免空指针异常,要对动态开辟的空间返回的指针进行空指针判断检查。(好习惯)

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

这里直接拿代码来解释吧!

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

这段代码中,我们使用malloc开辟40个字节大小的空间,然后进行空指针检查判断,紧接着,在我们赋值的时候,我们最多只能访问到p[9]这块空间,代码中我们i的最大值为10,此时很明显,就出现了越界访问。

总的来说:开辟多少空间,就只能使用多少空间,没有开辟的,属于操作系统的空间,我们不可以随便进行操作访问。

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

void test()
{int a = 10;int *p = &a;free(p);//ok?
}

在前面讲free的时候说过,free释放的空间,只能是动态内存开辟的空间,不能是静态区或者栈区开辟的空间,上面代码的例子中,a是局部变量,不是动态开辟的空间,所以不能被free释放。

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

int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("malloc");return 1;}int i = 0;for (i = 0; i < 5; i++){*p = i + 1;p++;  //此时p不再指向malloc开辟的空间的起始地址}//释放free(p);p = NULL;return 0;
}

上面代码中,我们使用malloc开辟一块空间,然后将起始地址返回给p,也就是说p指向molloc动态开辟的空间的起始地址,紧接着进行空指针检查判断,然后给p指向的空间进行赋值,但是,在赋值的过程中,p的指向发生了变化(如下图),不再指向malloc开辟的空间的起始地址,此时在对p指向的空间进行释放,这种做法是不被允许的,会报错。

总结:free中的参数只能是动态内存开辟空间的起始地址,对于动态开辟的空间的起始地址不要随便的更改指向。

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

void test()
{int *p = (int *)malloc(100);free(p);free(p);//重复释放
}

上面这种情况也是一直很低级的错误,也是不被允许的,会报错,最好的解决办法就是,在释放完之后,将p指针置为空,这样在后面多次释放也不影响,因为p已经是空指针了。

总结:不能对同一块动态内存多次释放,解决办法:在第一次释放完之后,将指针置为空指针(好习惯)

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

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

对于上面这个代码,在test这个函数种,我们开辟了100个字节的动态空间,但是在最后,我们并没有对这块空间进行free释放,这时候当我们跳出函数之后,指针变量p也销毁了,这个时候,p原来指向的空间我们根本找不到了,但是这块空间仍然存在,仍然被占用,只是我们丢失了它的起始地址,不能再对这块空间进行操作或者访问了,这就造成了这块空间仍然存在占用内存,但是我们却访问不到,并无法释放,这就是所谓的内存泄露。

针对这个问题的解决:在我们使用完动态空间之后,一定要进行free释放。

总结:动态开辟的空间一定要释放,并且正确释放(切记)

好了,今天的动态内存分配和管理讲到这里就结束了,听到这里,相信大家的一些关于动态内存分配管理的问题就会迎刃而解了吧。如果哪里有问题,欢迎在评论区留言。如果觉得小编写的还不错的,那么可以一键三连哦,您的关注点赞和收藏是对小编最大的鼓励。谢谢大家!!!

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

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

相关文章

搭建Web服务器并用cpolar发布至公网访问

本地电脑搭建Web服务器并用cpolar发布至公网访问 文章目录 本地电脑搭建Web服务器并用cpolar发布至公网访问前言1. 首先在电脑安装PHPStudy、WordPress、cpolar2. 安装cpolar&#xff0c;进入Web-UI界面3. 安装wordpress4. 进入wordpress网页安装程序5. 利用cpolar建立的内网穿…

TensorFlow2.1 模型训练使用

文章目录 1、环境安装搭建2、神经网络2.1、解决线性问题2.2、FAshion MNIST数据集使用 3、卷积神经网络3.1、卷积神经网络使用3.2、ImageDataGenerator使用3.3、猫狗识别案例3.4、参数优化 1、环境安装搭建 链接: Windows 安装Tensorflow2.1、Pycharm开发环境 2、神经网络 1…

【数据结构】堆(Heap)

一、堆的概念及结构 1、概念 堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵 完全二叉树的 数组对象。 堆是非线性数据结构&#xff0c;相当于一维数组&#xff0c;有两个直接后继。 如果有一个关键码的集合K { k₀&#xff0c;k₁&#xff0c…

关于openfeign调用时content-type的问题

问题1描述&#xff1a; 今天在A服务使用openfeign调用B服务的时候&#xff0c;发现经常会偶发性报错。错误如下&#xff1a; 情况为偶发&#xff0c;很让人头疼。 两个接口如下&#xff1a; A服务接口&#xff1a; delayReasonApi.test(student);就是使用openfeign调用B服务的…

Python接口自动化之request请求封装

我们在做自动化测试的时候&#xff0c;大家都是希望自己写的代码越简洁越好&#xff0c;代码重复量越少越好。那么&#xff0c;我们可以考虑将request的请求类型&#xff08;如&#xff1a;Get、Post、Delect请求&#xff09;都封装起来。这样&#xff0c;我们在编写用例的时候…

Python文件操作教程,Python文件操作笔记

文件的打开与关闭 想一想&#xff1a; 如果想用word编写一份简历&#xff0c;应该有哪些流程呢&#xff1f; 打开word软件&#xff0c;新建一个word文件写入个人简历信息保存文件关闭word软件 同样&#xff0c;在操作文件的整体过程与使用word编写一份简历的过程是很相似的…

爬虫逆向实战(十三)--某课网登录

一、数据接口分析 主页地址&#xff1a;某课网 1、抓包 通过抓包可以发现登录接口是user/login 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个password加密参数&#xff0c;还有一个browser_key这个可以写死不需要关心 请求头…

【11】Redis学习笔记 (微软windows版本)【Redis】

注意:官redis方不支持windows版本 只支持linux 此笔记是依托微软开发windows版本学习 一、前言 Redis简介&#xff1a; Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据结构存储系统&#xff0c;它也被称为数据结构服务器。Redis以键值对&am…

取证的学习

Volatility命令语法 1.判断镜像信息&#xff0c;获取操作系统类型 Volatility -f xxx.vmem imageinfo 在查到操作系统后如果不确定可以使用以下命令查看 volatility - f xxx.vmem --profile [操作系统] volshell 2.知道操作系统类型后&#xff0c;用–profile指定 volat…

【Oracle 数据库 SQL 语句 】积累1

Oracle 数据库 SQL 语句 1、分组之后再合计2、显示不为空的值 1、分组之后再合计 关键字&#xff1a; grouping sets &#xff08;&#xff08;分组字段1&#xff0c;分组字段2&#xff09;&#xff0c;&#xff08;&#xff09;&#xff09; select sylbdm ,count(sylbmc) a…

DR模式 LVS负载均衡群集

数据包流向分析&#xff1a; &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &#xff08;2&#xff09;Director Server 和 Re…

Docker 网络

目录 Docker 网络实现原理 Docker 的网络模式&#xff1a; 网络模式详解&#xff1a; 1&#xff0e;host模式 2&#xff0e;container模式 3&#xff0e;none模式 4&#xff0e;bridge模式 5&#xff0e;自定义网络 Docker 网络实现原理 Docker使用Linux桥接&#x…

Linux下如何修改CPU 电源工作模式

最近处理一起历史遗留问题&#xff0c;感觉很爽。 现象&#xff1a; 背景&#xff1a;设备采用ARM&#xff0c;即rk3568处理器&#xff0c;采用Linux系统&#xff1b;主要用于视觉后端处理 现象&#xff1a;当软件运行一段时间&#xff0c;大概1个小时&#xff08;也不是很固定…

考研算法第46天: 字符串转换整数 【字符串,模拟】

题目前置知识 c中的string判空 string Count; Count.empty(); //正确 Count ! null; //错误c中最大最小宏 #include <limits.h>INT_MAX INT_MIN 字符串使用发运算将字符加到字符串末尾 string Count; string str "liuda"; Count str[i]; 题目概况 AC代码…

国内的PMP有多少含金量?

1.PMP是什么 PMP&#xff08;Project Management Professional&#xff09;指项目管理专业人士资格认证。它是由美国项目管理协会&#xff08;PMI&#xff09;举办的项目管理专业人员&#xff08;PMP&#xff09;认证考试&#xff0c;在全球190多个国家和地区推广&#xff0c;…

vue 数字递增(滚动从0到)

使用 html <Incremental :startVal"0" :endVal"1000" :duration"500" />js&#xff1a; import Incremental from /utils/num/numViewjs let lastTime 0 const prefixes webkit moz ms o.split( ) // 各浏览器前缀let requestAnimatio…

[C++] string类的介绍与构造的模拟实现,进来看吧,里面有空调

文章目录 1、string类的出现1.1 C语言中的字符串 2、标准库中的string类2.1 string类 3、string类的常见接口说明及模拟实现3.1 string的常见构造3.2 string的构造函数3.3 string的拷贝构造3.4 string的赋值构造 4、完整代码 1、string类的出现 1.1 C语言中的字符串 C语言中&…

「Qt」文件读写操作

0、引言 我们知道 C 和 C 都提供了文件读写的类库&#xff0c;不过 Qt 也有一套自己的文件读写操作&#xff1b;本文主要介绍 Qt 中进行文件读写操作的类 —— QFile。 1、QFileDialog 文件对话框 一般的桌面应用程序&#xff0c;当我们想要打开一个文件时&#xff0c;通常会弹…

php+echarts实现数据可视化实例

效果&#xff1a; 代码&#xff1a; php <?php include(includes/session.inc); include(includes/SQL_CommonFunctions.inc); ?> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv&quo…

OpenLayers入门,OpenLayers加载google街景地图

专栏目录: OpenLayers入门教程汇总目录 前言 本章讲解OpenLayers加载google街景地图,无需科学上网,也可以正常访问瓦片。 二、依赖和使用 "ol": "^6.15.1"使用npm安装依赖npm install ol@6.15.1使用Yarn安装依赖yarn add olvue中如何使用: vue项…