C语言进阶——动态内存管理(上)

news2024/11/15 19:48:13

🌇个人主页:_麦麦_

📚今日名言:“你若爱,生活哪里都可爱。你若恨,生活哪里都可恨。你若感恩,处处可感恩。你若成长,事事可成长。不是世界选择了你,是你选择了这个世界。既然无处可躲,不如傻乐。既然无处可逃,不如喜悦。既然没有净土,不如静心。既然没有如愿,不如释然。”

                                                                                                          ——丰子恺《豁然开朗》

目录

​编辑

一、前言

二、正文

        1.内存的分布

        2.为什么存在动态内存开辟

        3.动态内存函数的介绍

                 3.1 malloc

                 3.2free

                3.3calloc

                 3.4realloc

         4.常见的动态内存的错误

                4.1对空指针的解引用操作

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

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

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

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

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

 三、结语


一、前言

  小伙伴们好呀,今天为大家带来的是动态内存的相关知识,主要围绕动态内存管理相关函数常见错误并伴有一定的题目练习,希望能够为读者们带来一定的收获。

二、正文

        1.内存的分布

        在具体讲解动态内存管理之前,需要先给小伙伴们铺垫一下内存空间的分布(简略版)。在之前的学习中,我们认识了局部变量,全局变量等,那它们在内存中处于何种位置呢?

        在内存中可以粗略地分为三大块:栈区堆区静态区。栈区中存放的大多是一些局部变量,函数的形式参数;而我们下面所要学习的与动态内存管理相关的函数则存在与堆区;最后的静态区则存放静态变量与全局变量

 

        2.为什么存在动态内存开辟

         在了解完大致的内存分布后,接下来就是动态内存管理了。我们都知道无论是创建一个整形变量亦或是一个指定大小的数组,都是需要向内存申请相应大小的空间,也就是在内存里开辟空间。而目前我们已经掌握的内存开辟方式有:

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

        我们会上述的开辟空间的方式有两个特点:

1.空间开辟大小是固定的

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

        但是对于空间的需求,不仅仅是上述的情况,有时候我们所需要的空间大小在程序运行的时候才能知道,那么采用数组的编译时就开辟空间的这种方式就不能满足这种需求。这个时候,就只能试试动态内存开辟了。

        3.动态内存函数的介绍

        而C语言正好提供了与动态内存开辟相关的函数,下面就即将展开相关函数的学习。

                 3.1 malloc

void* malloc(size_t size);

        这个函数是向内存申请一块连续可用的空间,并返回指向这块空间的指针

●引用的头文件:<stdlib.h>

●如果开辟成功,则返回一个指向开辟好空间的指针

●如果开辟失败,则返回一个指向NULL指针,因此malloc的返回值一定要做检查

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

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

        下面演示一下malloc函数的简单使用:

//malloc函数使用示例
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
	//申请40个字节,用来存放10个整型
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//存放1-10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//打印
	for(i = 0; i < 10;i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

 

注:当申请完空间后要再主动地返回空间,虽然程序结束操作系统会自动回收,但是如果程序一直不结束,那么这部分空间就会一直被闲置,那么如何回收呢?

                 3.2free

void free (void * ptr);

        C语言中提供了另外一个函数free,专门是用来做动态内存的释放和回收的。free函数用来释放动态内存开辟的内存。

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

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

#include <string.h>
int main()
{
	//申请40个字节,用来存放10个整型
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//存放1-10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//打印
	for(i = 0; i < 10;i++)
	{
		printf("%d ", *(p + i));
	}
	//free是释放申请的内存
	free(p);
	return 0;
}

 

        但是我们发现虽然我们已经用free释放了申请的空间,但是p仍存储着指向我们所申请的空间的地址,也就是存在非法访问的的风险,所以一般情况下,当我们使用free释放动态内存之后,还要将指向那块空间的指针置空。

//malloc函数使用示例
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
	//申请40个字节,用来存放10个整型
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//存放1-10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//打印
	for(i = 0; i < 10;i++)
	{
		printf("%d ", *(p + i));
	}
	//free是释放申请的内存
	free(p);
	p = NULL;
	return 0;
}

                3.3calloc

void * calloc(size_t num,size_t size);

        除了malloc之外,C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配

●calloc的功能是为num个大小为size的元素开辟空间,并且把空间的每个字节初始化为0

●calloc与malloc的区别只在于在calloc会在返回地址之前把申请的空间的每个字节初始化全0

#include <stdlib.h>
int main()
{
	int* p = calloc(10, sizeof(int));
	if (NULL!=p)
	{
		//使用空间
	}
	free(p);
	p = NULL;
}

 

         那么在开辟动态内存空间的时候在mallo和calloc两个函数中该如何选择呢?主要是根据自己的需求来考虑。

●malloc无需初始化直接返回地址——效率比较高

●calloc会自动初始化为0

     

                 3.4realloc

void * realloc(void * ptr, size_t size);

●realloc函数的出现让动态内存管理更加灵活

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

        下面具体来认识下realloc函数

●ptr是要调整的内存地址

●size为调整之后新的大小

●返回值为调整之后的内存其实位置

●这个函数在调整原内存大小的基础上,还会将原来内存中数据移动到新的空间

●ralloc在调整内存空间的时候存在两种情况

        (1)原有空间之后有足够大的空间

        (2)原有空间之后没有足够大的空间

 

         那么realloc会如何处理这两种情况呢?

情况1:当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回的是原有的起始地址

情况2:当是情况2的时候,原有空间之后没有足够多的空间时,拓展的方法是:在对科技上另找一个合适大小的连续空间来使用,这样的话函数返回的是一个新的内存地址

        由于上述的两种情况,当我们使用realloc函数就和之前有一些区别。那么具体该如何使用,具体代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

//realloc的使用方式1
int main()
{
	int* p = (int*)malloc(5 * sizeof(int));
	if (NULL == p)
	{
		perorr("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = 1;
	}
	//不够了,增加5个整型的空间
	p=realloc(p, 10 * sizeof(int));
	//继续使用空间
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放空间
	free(p);
	p = NULL;
	return 0;
}

//realloc的使用方式2
int main()
{
	int* p = (int*)malloc(5 * sizeof(int));
	if (NULL == p)
	{
		perorr("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = 1;
	}
	//不够了,增加5个整型的空间
	int* ptr = realloc(p, 10 * sizeof(int));
	if (ptr != NULL)
	{
		p = ptr;
	}
	//继续使用空间
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放空间
	free(p);
	p = NULL;
	ptr = NULL;
	return 0;
}

         第一次使用的小伙伴在使用realloc函数的时候很有可能就是按照第一种方式来写的,而第一种方式和第二种方式的区别在于对realloc函数返回的指针的接受方法不同。前者是直接用第一次开辟的空间的指针来接受,而后者则是使用了一个新指针来接受返回的指针

        那么到底哪种方式更好呢,在认识到realloc函数返回的不同情况下,方式2是一种更好的情况。若是采取方式1,一旦realloc函数扩容失败,就会返回一个空指针,而且会把原来的指针置空,相当于把原来的数据丢失了,可以说是很危险的一种情况,而方式2恰恰避免了这种情况。

         4.常见的动态内存的错误

        在掌握了动态内存函数的使用方法之后,可能有的小伙伴在后续的书写中还会遇到代码错误的问题,以下列举了在使用动态内存函数会出现的常见错误,来帮助大家更好的规避错误,写出优秀的代码。

                4.1对空指针的解引用操作

//对NULL指针的解引用操作
void text1()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

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

//对动态开辟空间的越界访问
void text2()
{
	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);
	p = NULL;
}

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

//对非动态开辟内存使用free释放
void text3()
{
	int a = 0;
	int* p = &a;    
	free(p);    //释放错误
}

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

//使用free释放一块动态开辟内存的一部分
void text4()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);	//p不在指向动态内存的起始位置
	p = NULL;
}

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

//对同一块动态内存多次释放
int main()
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		return 1;
	}
	//使用
	//释放
	free(p);
	//..

	free(p);
	return 0;
}

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

//动态开辟内存忘记释放(内存泄漏)
void text()
{
	int* p = (int*)malloc(100);
	//使用
	//......
}
int main()
{
	text();
	//..
	return 0;
}

         在这种情况下,一旦第一次忘记释放,第二想再释放都释放不了,除非程序结束自动释放。那么有什么好的方法来避免上述情况吗?

//第一层保障:malloc和free成对使用
void text()
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		return 1;
	}
	//使用
	free(p);
	p = NULL;
}

int main()
{
	text();
	//....
	return 0;
}

//第二层:将指向动态开辟内存的指针返回去,谁接受谁后面去释放,所以这种函数一定要写注释
int* text()
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		return 1;
	}
	//使用
	return p;
}

 三、结语

         到此为止,关于动态内存管理的讲解就已完成一部分了。

         关注我 _麦麦_分享更多干货:_麦麦_的博客_CSDN博客-领域博主
         大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!

 

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

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

相关文章

mdio协议

1. 简介 MDIO接口中有特定的术语定义总线上的各种设备&#xff0c;驱动MDIO总线的设备被定义为站管理实体&#xff08;STA&#xff09;&#xff0c;而被MDC管理的目标设备称为可被MDIO管理的设备&#xff08;MMD&#xff09;。 STA初始化MDIO所有的通信&#xff0c;同时负责驱动…

【数据结构与算法】哈希表1:字母异位词 两数交集 快乐数 两数之和

文章目录今日任务1.哈希表理论基础&#xff08;1&#xff09;哈希表&#xff08;2&#xff09;哈希函数&#xff08;3&#xff09;哈希碰撞&#xff08;4&#xff09;链地址法&#xff08;拉链法&#xff09;&#xff08;5&#xff09;线性探测法&#xff08;6&#xff09;常见…

Python采集双色球数据,做数据分析,让我自己实现自己的富豪梦

来唠点嗑&#xff1f; 咳咳&#xff0c;最近是咋的了&#xff0c;某站掀起了一股双色球热潮&#xff1f;一般我自己的账号上&#xff0c;是很少看到关于python这些内容的&#xff0c;都是小姐姐和热梗&#xff0c;或者其他搞笑视频 由于&#x1f4b4;的吸引力…手不自觉的就点…

《系统架构设计》-03-软件结构体系和架构风格

文章目录1. 软件结构体系1.1 抽象&#xff08;Abstract&#xff09;1.1.1 抽象的应用1.1.2 不同层次的抽象1.2 组件&#xff08;Component&#xff09;1.2.1 定义1.2.2 切入点1.3 组织过程资产&#xff08;Organizational Process Assets&#xff09;1.3.1 定义1.3.2 作用1.4 体…

springboot整合Chat Generative Pre-trained Transformer

什么是Chat Generative Pre-trained Transformer Chat Generative Pre-trained Transformer&#xff0c;是以人工智能驱动的聊天机器人程序 &#xff0c;已经更新多个版本&#xff0c;很多大厂也都在接入其API。 整合难度 难度一颗星&#xff0c;基本上就是给官方API发请求&am…

特征工程:特征构造以及时间序列特征构造

数据和特征决定了机器学习的上限&#xff0c;而模型和算法只是逼近这个上限而已。由此可见&#xff0c;特征工程在机器学习中占有相当重要的地位。在实际应用当中&#xff0c;可以说特征工程是机器学习成功的关键。 那特征工程是什么&#xff1f; 特征工程是利用数据领域的相关…

UI自动化测试之设计框架

目的 相信做过测试的同学都听说过自动化测试&#xff0c;而UI自动化无论何时对测试来说都是比较吸引人的存在。 相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟&#xff0c;那么存在即合理&#xff0c;自动化UI测试自然也是广大测试同学职…

身为大学生,你不会还不知道有这些学生福利吧!!!!

本文介绍的是利用学生身份可以享受到的相关学生优惠权益&#xff0c;但也希望各位享受权利的同时不要忘记自己的义务&#xff0c;不要售卖、转手自己的学生优惠资格&#xff0c;使得其他同学无法受益。 前言 高考已经过去&#xff0c;我们也将迎来不同于以往的大学生活&#x…

磁盘结构

一.盘片 盘片是一个圆形坚硬的表面&#xff0c;通过引入磁性变化来永久存储数据&#xff0c;这些盘片通常由一些硬质材料&#xff08;如铝&#xff09;制成&#xff0c;然后涂上薄薄的磁性层&#xff0c;即使驱动器断电&#xff0c;驱动器也能持久存储数据位。每个盘片有两面&a…

袋鼠云高教行业数字化转型方案,推进数字化技术和学校教育教学深度融合

在当前的数字化转型浪潮下&#xff0c;“基础设施、配套设备、应用探索”的数字校园1.0阶段即将步入尾声、亦或已经完结&#xff0c;不同地区和类型的高校通过各类信息化系统和基础设施已经初步实现了业务数字化&#xff0c;整个数字校园的信息基础设施底座已有一定基础、信息时…

TCP编程之网卡信息获取和域名解析

TCP编程之网卡信息获取和域名解析 1.TCP/IP简介 TCP/IP协议源于1969年&#xff0c;是针对Internet开发的一种体系结构和协议标准&#xff0c;目的在于解决异种计算机网络的通信问题。使得网络在互联时能为用户提供一种通用、一致的通信服务。是Internet采用的协议标准。   …

三菱PLC的MC协议配置说明

三菱PLC的MC协议配置说明先说一下弱智的踩坑记录详细配置过程1、三菱Q02H CPUQJ71E71-100以太网模块设置MC协议1.1 PLC编程线连接与编程线驱动安装1.2 PLC通讯测试1.3 PLC MC协议设置1.4 PLC断点重启1.5 网络调试助手测试2、三菱Q03UDE CPU内置以太网设置MC协议2.1 PLC编程线连…

决策树算法和CART决策树算法详细介绍及其原理详解

相关文章 K近邻算法和KD树详细介绍及其原理详解朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解决策树算法和CART决策树算法详细介绍及其原理详解 文章目录相关文章前言一、决策树算法二、CART决策树算法2.1 基尼系数2.2 CART决策树算法总结前言 今天给大家带来的主要内容包…

虹科分享 | 网络流量监控 | 你的数据能告诉你什么:解读网络可见性的4种数据类型

要了解网络性能问题的原因&#xff0c;可见性是关键。而这四种数据类型&#xff08;流、数据包、SNMP和API&#xff09;都在增强网络可见性方面发挥着重要作用。 流 流是通过网络发送的数据的摘要。流类型不同&#xff0c;可以包括NetFlow, sFlow, jFlow和IPFIX。不同的流类型…

SPF动物实验室设计,SPF动物实验室装修SICOLAB

SPF&#xff08;特殊病原体自由&#xff09;动物实验室规划设计SICOLAB&#xff08;一&#xff09;设计原则为了建造一座SPF&#xff08;特殊病原体自由&#xff09;动物实验室&#xff0c;需要采取以下步骤&#xff1a;&#xff08;1&#xff09;选址&#xff1a;选择远离其他…

lombok注解@Data使用在继承类上时出现警告解决方案

lombok为我们提供了Data注解&#xff0c;帮助我们省略了Setter,Getter,ToString等注解&#xff0c;一般对于普通的实体类使用该注解&#xff0c;不会出现什么问题&#xff0c;但是当我们把这个注解&#xff0c;使用在派生类上&#xff0c;就出现了一个警告1 情景再现父类:Data …

SESAM 安装教程

SESAM &#xff08;Super Element Structure Analysis Module&#xff09;是由挪威船级社&#xff08;DNV-GL&#xff09;开发的一款有限元分析&#xff08;FEA&#xff09;系统&#xff0c;它以 GeniE、HydroD 和 DeepC 等模块为核心&#xff0c;主要用于海工结构的强度评估、…

leaflet 绘制两个多边形的交集、差集、并集(083)

第083个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中如何获取两个多边形的交集、差集、并集。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共148行)安装插件相关API参考:专栏目标示例效果 配…

Python日期时间模块

Python 提供了 日期和时间模块用来处理日期和时间&#xff0c;还可以用于格式化日期和时间等常见功能。 时间间隔是以秒为单位的浮点小数。每个时间戳都以自从 1970 年 1 月 1 日午夜&#xff08;历元&#xff09;经过了多长时间来表示。 一、time模块使用 Time 模块包含了大…

使用ribbon实现负载均衡

1.新建两个provider&#xff1a;springcloud-provider-dept-8002 2. 配置跟8001一样 整合 Ribbon 由上述可知&#xff0c;Ribbon 是需要集成在消费端的 所以在消费端 &#xff1a; springcloud-03-consumer-dept-8082 进行修改 在 POM 文件中添加 Ribbon、Eureka 依赖 <!--…