动态内存管理篇

news2024/11/29 5:25:12

为什么要动态内存分配?

之前,我们向内存申请空间,有两种方式,一种是定义变量,一种是建立数组;但是,这两种方式都有缺陷,创建的空间大小是固定的,在程序的运行过程中,不能随着我们的需要改变而改变,这就需要我们申请动态内存了

1. 动态内存函数


1.1 malloc和free

void* malloc (size_t size);

函数功能:

  • 开辟一块size字节大小的空间
  • 如果开辟成功,返回开辟空间首地址
  • 如果开辟失败,返回NULL
  • 如果size是0,标准未定义,取决于编译器
  • 由于返回值是void*类型,因此返回的地址需要我们另做处理
void free (void* ptr);

函数功能:

  • 释放ptr指向的空间,前提是ptr指向的空间是动态开辟的;如果ptr指向的空间不是动态开辟的,编译器会报错
  • 如果ptr为NULL,则什么都不做
  • 另外,释放完空间,ptr此时是一个野指针,需要置空

动态内存函数要和free一同使用,动态开辟的空间有两种方式释放:

  1. free主动释放
  2. 程序结束,操作系统会帮我们回收

虽然程序结束,申请的动态空间也会被回收,但如果程序在退出之前,开辟了多处动态内存而没有释放,又去开辟动态内存,很可能会导致内存泄漏,因此,每次申请的动态内存都要记得free释放

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
	}

	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL;

	return 0;
}
//输出:0 1 2 3 4 5 6 7 8 9

内存非为三大部分,动态内存函数申请的空间是在堆上开辟的在这里插入图片描述

1.2 calloc函数

void* calloc (size_t num, size_t size);

函数功能:

  • num是开辟元素的个数,size是每个元素的大小,开辟num*size字节的空间
  • calloc开辟空间后,会将空间自动初始化为0

在这里插入图片描述

1.3 realloc函数

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

动态开辟的内存用完了,想进行增容,这时就可以考虑使用realloc

函数功能:

  • ptr是要进行扩容的地址,size为新的空间大小
  • 返回新空间的地址
  • 如果ptr为空,此时就相当于malloc函数

开辟空间有两种情况:

  1. ptr后面的空间够存放新空间的大小:此时直接在ptr后面的空间扩容
  2. ptr后面的空间不够存放新空间的大小:此时会另开辟一块size大小的空间,并把原数据拷贝到新空间,释放掉旧空间,返回新空间的地址
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	//...

	//想扩容到100个int的大小
	//写法1
	p = realloc(p, 100 * sizeof(int));

	//写法2
	int* ptr = (int*)realloc(p, 100 * sizeof(int));
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	p = ptr;
	//...

	free(p);
	p = NULL;

	return 0;
}

用realloc开辟完空间,更推荐使用写法2,因为realloc开辟空间可能会失败,此时返回NULL,不仅没有扩容成功,还把原来的空间给弄没了

2.动态内存常见的错误


2.1对空指针进行解引用

这种情况通常是因为使用完动态内存函数没有对返回值进行检查

在这里插入图片描述

2.2对动态内存的越界访问

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	for (int i = 0; i <= 10; i++)
	{
		*(p + i) = i;
	}
    
    free(p);
    p = NULL;

	return 0;
}

i=10的内存已经不属于我们的了;这种情况编译器是不会报错的,需要我们自己擦亮眼睛

2.3对非动态内存使用free

int main()
{
	int p[10] = { 0 };
	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
	}

	free(p);

	return 0;
}

p指向的空间不是动态开辟的,不能进行free释放

2.4使用free释放一部分动态内存

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	for (int i = 0; i < 5; i++)
	{
		*p = i;
		p++;
	}

	free(p);
	p = NULL;

	return 0;
}

p最终指向动态内存的一部分,free§只释放了一部分,最终仍有可能造成内存泄漏

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	//...

	free(p);

	//...

	free(p);
	p = NULL;

	return 0;
}

前面说过,free指向的空间必须是动态内存,第二次free时,p指向的空间已经不是动态的了

2.6忘记释放动态内存

void Print(int n)
{
	int* p = (int*)malloc(n * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return;
	}

	if (n == 3)
		return;

	free(p);
	p = NULL;
}

int main()
{
	Print(3);

	return 0;
}

上面的代码写了free且置空,看似没有问题,但在执行free之前,函数已经返回,不会执行free,因此没有释放成功

内存泄漏是非常严重的问题,在日常写代码的过程中,一定要注意,动态开辟的内存要记得释放

3.笔试题讲解


题目1:

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}

void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

int main()
{
	Test();
	return 0;
}

上述代码的运行结果是什么?

  • 由于p是str的一份临时拷贝,出了GetMemory函数就销毁了,malloc开辟出来的空间就找不到了,导致内存泄漏
  • 执行完GetMemory函数,str仍然是NULL,在strcpy函数中,会对str解引用,对空指针进行解引用,最终导致程序崩溃

怎么修改上述代码,让它达到我们想要的功能?

char* GetMemory()
{
	char* p = (char*)malloc(100);

	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	strcpy(str, "hello world");
	printf(str);
    
    free(str);
    str = NULL;
}

int main()
{
	Test();
	return 0;
}

题目2:

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

int main()
{
	Test();
	return 0;
}

p是GetMemory函数的局部变量,出了函数就销毁了,此时返回的p被外面的str接受,str就变成了野指针,打印的是一堆乱码,这是一种返回栈空间地址的问题

上面的代码也可以简化为:

int* Test()
{
	int a = 10;
	return &a;
}

int main()
{
	int* p = Test();
	printf("%d\n", *p);//10

	return 0;
}

同样是犯了返回栈空间地址的错误,我们发现该代码输出是正常的,这是为什么?

虽然结果正确,但这并不代表代码没有问题,结果正确的原因是Test函数即使销毁了,p位置处的值仍没有被修改,因此误打误撞,结果是对的在这里插入图片描述

题目3:

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

int main()
{
	Test();
	return 0;
}

该代码正常打印,唯一的缺点就是少了释放内存,存在内存泄漏的问题

题目4:

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();
	return 0;
}

str指向的空间释放后,没有置空,str此时是野指针,对野指针进行了访问,非法访问内存空间

题目5:

int* Test()
{
	int* p;
	*p = 10;
	return p;
}

创建p时没有对其初始化,p为随机值,随机指向一块空间,是野指针,对野指针进行了操作

4.C/C++的内存区域


在这里插入图片描述

  • 栈区:执行函数时,函数内的局部变量都是在该区域创建的,当函数结束时,自动销毁创建的区域
  • 堆区:动态开辟的空间在该区域创建,通常由程序员自己释放,当程序结束时,也会由操作系统自动回收
  • 数据段:存放全局变量,静态数据,程序结束由系统释放
  • 代码段:存放函数体的二进制代码

6.柔性数组

定义:在结构体中,最后一名成员是数据,且数组的大小未知,我们把该数组叫做柔性数组

struct S
{
	char c;
	int i;
	int arr[];
};

6.1柔性数组的特点

  1. 柔性数组必须在结构体当中
  2. 必须是最后一名成员
  3. 柔性数组前面至少有一名成员
  4. 该结构体的大小不包括数组的大小
  5. 必须用动态内存函数对结构体开辟空间,且开辟空间的大小要大于结构体的大小,确保柔性数组有有一定的空间

6.2柔性数组的使用

//代码1
struct S
{
	char c;
	int i;
	int arr[];
};

int main()
{
	struct S* p;
	p = (struct S*)malloc(sizeof(struct S) + 20);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	p->c = 'a';
	p->i = 5;
	for (int i = 0; i < 5; i++)
	{
		p->arr[i] = i;
	}

	//空间不够了,进行增容
	struct S* ptr = (struct S*)realloc(p, sizeof(struct S) + 40);
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	p = ptr;
	ptr = NULL;
	for (int i = 0; i < 10; i++)
	{
		p->arr[i] = i;
	}

	free(p);
	p = NULL;

	return 0;
}

实际上,不用柔性数组也能完成上面的操作

//代码2  
struct S  
{
	char c;  
	int i;  
	int* arr;  
};

int main()  
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));


	if (ps == NULL) 
	{
		perror("malloc");
		return 1;
	}

	int* ptr = (int*)malloc(sizeof(int) * 5);
	if(ptr == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->arr = ptr;
	ptr = NULL;

	for (int i = 0; i < 5; i++)
	{
		ps->arr[i] = i;
	}
	
	//增容
	int* p = (int*)realloc(ps->arr, sizeof(int) * 10);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->arr = p;
	p = NULL;

	for (int i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}

	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;
}

那么,使用柔性数组的代码1相较于代码2,有什么优势呢?

  1. 方便内存的释放,使用柔性数组只需要释放一次内存空间;而代码2你必须先将结构体成员开辟的空间释放后,才能释放结构体,多释放意味着风险越多

动态内存的内容就到这!

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

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

相关文章

操作系统(Operator System)

这里写目录标题 1. 什么是操作系统2. 主要功能3. 计算机的层状结构4. 什么叫做管理5. 总结6. 为什么要有操作系统7. 最后 1. 什么是操作系统 操作系统&#xff08;英语&#xff1a;Operating System&#xff0c;缩写&#xff1a;OS&#xff09;是一组主管并控制计算机操作、运…

PostgreSQL PG的流复制搭建

注: 本文为云贝教育 刘峰 原创&#xff0c;请尊重知识产权&#xff0c;转发请注明出处&#xff0c;不接受任何抄袭、演绎和未经注明出处的转载。【PostgreSQL】PG的流复制搭建 - 课程体系 - 云贝教育https://www.yunbee.net/Home/News/detail/article_id/510.html 一 、主备机…

HarmonyOS资源分类与访问

资源分类与访问 应用开发过程中&#xff0c;经常需要用到颜色、字体、间距、图片等资源&#xff0c;在不同的设备或配置中&#xff0c;这些资源的值可能不同。 应用资源&#xff1a;借助资源文件能力&#xff0c;开发者在应用中自定义资源&#xff0c;自行管理这些资源在不同…

仿网易云音乐网站PHP源码,可运营的原创音乐分享平台源码,在线音乐库系统

源码介绍 使用PHP和MYSQL开发的原创音乐分享平台源码&#xff0c;仿网易云音乐网站。用户可以在网站上注册并上传自己的音乐作品&#xff0c;系统内置广告系统&#xff0c;为网站创造收入来源。 安装教程 1.导入sql.sql 2.修改 includes\config.php 数据库信息和网址都改成…

【教3妹学编程-算法题】一年中的第几天

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开森。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;经过了一周多的寒潮&#xff0c;天气总算暖和些了。 2哥&#xff…

最新ChatGPT网站AI系统源码,附详细搭建教程/支持GPT4.0/AI绘画/GPT语言对话/DALL-E3文生图/自定义知识库

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

搭建 Mac系统Arduino + MindPlus开发环境

搭建 Mac系统Arduino MindPlus开发环境 1.概述 2024年1月1号&#xff0c;元旦大家的安排丰富多彩&#xff0c;在这一天中我的安排依旧坚持初心&#xff0c;牢记使命。学习是我的起点也是我的终点&#xff0c;只要活着就要用知识丰富自己的生活。 今天是一个有意义的日子&…

GVRP实验配置

GVRP&#xff08;GARP VLAN Registration Protocol&#xff09;&#xff0c;称为VLAN注册协议。 GVRP基于GARP的工作机制&#xff0c;是GARP的一种应用。GVRP用来维护交换机中的VLAN动态注册信息&#xff0c;并传播该信息到其它的交换机中。支持GVRP特性的交换机能够接收来自其…

Servlet见解3

13 Cookie和Session http协议是一个无状态的协议&#xff0c;你每一个跳转到下一个页面的时候都是需要先登录才能使用&#xff0c;这样就很麻烦比如淘宝&#xff0c;没有cookie和session的话&#xff0c;用户在首页已经登录上去了&#xff0c;但是需要再次登录才能选择商品&am…

无监督关键词提取算法:TF-IDF、TextRank、RAKE、YAKE、 keyBERT

TF-IDF TF-IDF是一种经典的基于统计的方法&#xff0c;TF(Term frequency)是指一个单词在一个文档中出现的次数&#xff0c;通常一个单词在一个文档中出现的次数越多说明该词越重要。IDF(Inverse document frequency)是所有文档数比上出现某单词的个数&#xff0c;通常一个单词…

JMeter(十六)-JMeter断言

十六、JMeter断言 1.简介 断言组件用来对服务器的响应数据做验证&#xff0c;常用的断言是响应断言&#xff0c;其支持正则表达式。虽然我们的通过响应断言能够完成绝大多数的结果验证工作&#xff0c;但是JMeter还是为我们提供了适合多个场景的断言元件&#xff0c;辅助我们来…

Redis原理及常见问题

高性能之道 单线程模型基于内存操作epoll多路复用模型高效的数据存储结构redis的单线程指的是数据处理使用的单线程,实际上它主要包含 IO线程:处理网络消息收发主线程:处理数据读写操作,包括事务、Lua脚本等持久化线程:执行RDB或AOF时,使用持久化线程处理,避免主线程的阻…

服务器监控软件夜莺部署(一)

文章目录 一、夜莺介绍1. 简介2. 相关网站 二、夜莺部署1. 部署架构2. Docker启动3. 配置数据源4. 内置仪表盘效果5. 时序指标效果 一、夜莺介绍 1. 简介 夜莺监控系统是一款专业的服务器监控软件&#xff0c;它可以帮助用户实时监测服务器的CPU、内存、磁盘利用率等。 夜莺监…

二叉树题目:根到叶路径上的不足结点

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;根到叶路径上的不足结点 出处&#xff1a;1080. 根到叶路径上的不足结点 难度 6 级 题目描述 要求 给定二叉树的根结点 root \texttt{root} root…

C语言之sizeof详解,5点透析,帮你真正了解它

今天也要继续坚持 前言 今天复习C语言了解到不少和她有关的知识&#xff0c;才知道之前对他了解甚少&#xff0c;于是写下博客及时记录自己的所得&#xff0c;与大家分享一下 第一点&#xff1a;sizeof不是函数 sizeof是一个关键字而不是函数&#xff01;是的&#xff0c;他…

【AIGC矢量风格】黑色和白色一系列物体

基于矢量风格的一组画面&#xff1a; 矢量风格是海报设计中常见的一种风格&#xff0c;它主要使用矢量图形进行设计。矢量图形是由数学公式定义的图形&#xff0c;其特点是可以在不失去清晰度的情况下进行任意缩放&#xff0c;无论图形尺寸如何变化&#xff0c;都不会失真或模糊…

猫咪训练的方法指南,新手养猫的攻略大全

一、教程描述 本套教程可以教你学会养猫&#xff0c;让您快速成长为养猫专家。视频教程是猫咪基础训练&#xff0c;共有7个视频&#xff0c;电子书教程是养猫攻略大全&#xff0c;共有11本&#xff0c;包括爱猫养护实用手册&#xff0c;全世界250多种猫的彩色图鉴&#xff0c;…

解密C++中的forward<int>(a)和forward<int >(a):你真的了解它们之间的区别吗?

一文看尽C中的forward完美转发 一、前言二、深入理解forward和完美转发三、对forward<int>(a)的解析四、对forward<int &&>(a)的解析五、forward<int>(a)和forward<int &&>(a)的区别总结 一、前言 完美转发在C中具有重要性&#xff0…

kafka 的零拷贝原理

文章目录 kafka 的零拷贝原理 今天来跟大家聊聊kafka的零拷贝原理是什么&#xff1f; kafka 的零拷贝原理 零拷贝是一种减少数据拷贝的机制&#xff0c;能够有效提升数据的效率&#xff1b;   在实际应用中&#xff0c;如果我们需要把磁盘中的某个文件内容发送到远程服务器上…

zlib.decompressFile报错 【Bug已解决-鸿蒙开发】

文章目录 项目场景:问题描述原因分析:解决方案:方案1方案2此Bug解决方案总结寄语项目场景: 最近也是遇到了这个问题,看到网上也有人在询问这个问题,本文总结了自己和其他人的解决经验,解决了zlib.decompressFile报错 的问题。 问题: zlib.decompressFile报错,怎么解…