C语言【动态内存】

1.为什么要有动态内存

我们现在掌握的内存开辟方法有:

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

但是上述的方式有两个点要注意:

1.空间开辟的大小是固定的
2.数组在申明的时候,一定要指定数组的长度(就算是变长数组也是先给变量赋值,再根据变量的大小,来开辟空间),数组空间一旦确定了大小是不能调整的

但是我们在实际上对于内存的需求方式,绝不仅仅是上述的情况。有时候我们需要的空间大小是在程序运行的时候才能知道,数组是在编译时开辟空间的,这是就不能满足需求了。
这时C语言就引入了动态内存开辟,让程序员可以自己根据需求来申请和释放空间这样就比较灵活。

2.动态内存函数

要想学会动态开辟内存就必须掌握这四个函数malloc free calloc realloc

它们的头文件都为<stdlib.h>
他们开辟空间的大小单位都为字节。

2.1 malloc

函数原型:void* malloc(size_t size);
该函数向内存申请一块连续何用的空间,并返回指向这块空间的指针。

  1. 如果开辟成功,返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则会返回一个NULL指针,因此malloc的返回值一定要做检查。

因为返回类型是void*,所以malloc函数并不知道开辟空间时是什么类型,在实际使用时类型是有使用者决定的。

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

代码如下:

#include<stdio.h>
#include<stdilb.h>
int main()
{
	int* parr = (int*)malloc(10 * sizeof(int));
	if (parr == NULL)
	{
		perror("malloc");
		exit(1);
	}
	for (int i = 0; i < 10; i++)
	{
		//初始化
		*(parr + i) = i + 1;
		//打印
		printf("%d ", *parr + i);
	}

	return 0;
}

在这里插入图片描述

注意:malloc开辟的空间并未进行初始化,里面是随机值

#include<stdio.h>
#include<stdilb.h>
int main()
{
	int* parr = (int*)malloc(10 * sizeof(int));
	if (parr == NULL)
	{
		perror("malloc");
		exit(1);
	}
	for (int i = 0; i < 10; i++)
	{
		//初始化
		*(parr + i) = i + 1;
		//打印
		printf("%d ", *parr + i);
	}

	return 0;
}
#include<stdio.h>
#include<stdilb.h>
int main()
{
	int* parr = (int*)malloc(10 * sizeof(int));
	if (parr == NULL)
	{
		perror("malloc");
	}
	for (int i = 0; i < 10; i++)
	{
		//未进行初始化
		//打印
		printf("%d ", *parr + i);
	}

	return 0;
}

结果如下:
在这里插入图片描述

这些动态开辟的空间是存放在哪里呢?

如图:
在这里插入图片描述

2.2 free

申请了空间在使用完的时候肯定是要还回去的嘛,那怎么还呢?

C语言提供了另外一个函数free,这是专门用来做动态内存的释放和回收的,函数原型如下:
void free(void* ptr)

  • 如果参数ptr指向的空间不是动态内存的时候,free函数的行为是未被定义的
  • 如果参数ptrNULL指针,那么free函数什么都不会做

注意:传递给free函数的参数是要被释放空间的起始地址
代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* parr = (int*)malloc(10 * sizeof(int));
	if (parr == NULL)
	{
		perror("malloc");
	}
	for (int i = 0; i < 10; i++)
	{
		//初始化
		*(parr + i) = i + 1;
		//打印
		printf("%d ", *parr + i);
	}

	//只用完动态申请的空间要进行释放
	free(parr);

	parr = NULL;
	return 0;
}

​free仅仅是将空间的使用权限还给了操作系统;
​但parr还指向原来的地址,这就成为野指针;
为了避免成为野指针,及时将parr置为NULL。

如果一直不用函数free来释放申请的空间,这样很可能会造成内存泄漏!!!

2.3 calloc

C语言还提供了一个函数叫calloccalloc函数也用来动态内存分配。原型如下:
void * calloc(size_t num, size_t size);

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

由于函数malloc比函数calloc少一步(将内存初始化),所以malloccalloc更快,如果更追求效率的话可以使用malloc,如果不想自己初始化的话可以使用calloc

代码如下:

int main()
{
	int* pca = (int*)calloc(10,sizeof(int));
	if (pca == NULL)//判断是否开辟失败
	{
		perror("calloc");
		exit(1);//直接退出程序
	}
	printf("calloc: ");
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *pca + i);
	}
	printf("\n\n");


	int* pma = (int*)malloc(10 * sizeof(int));
	if (pma == NULL)
	{
		perror("malloc");
		exit(1);
	}
	printf("malloc: ");
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *pma + i);
	}

	return 0;
}

结果如图:
在这里插入图片描述

2.4 realloc

realloc函数能让动态内存管理更加灵活。

有些时候我们会发现我们申请的空间太小了,有时候又感觉申请的空间太大了,那么为了合理的使用空间,我们就会对申请的空间大小进行灵活的调整,那么realloc函数就能做到对动态空间大小的调整。

函数原型如下:
void* realloc(void* ptr, size_t size)

  • ptr是要被调整的内存地址
  • size是调整之后该内存的大小
  • 返回值为调整之后的内存空间的起始位置

2.4.1 realloc在调整内存空间有三种情况

  1. 原空间后面有足够的空间用来调整。
  2. 原空间后面没有足够空间用来调整,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
  3. 空间调整失败,返回NULL(这是最坏的情况)
    如图所示
    在这里插入图片描述
    代码如下:
#include<stdio.h>
#include<stdlib.h>

int main()
{
	//开辟
	int* parr = (int*)malloc(5 * sizeof(int));
	if (parr == NULL)
	{
		perror("malloc");
	}
	//使用
	for (int i = 0; i < 5; i++)
	{
		*(parr + i) = i + 1;
	}
	//假设要多插入5个int类型的数据
	//这时空间就不够了,需要调整

	
	int* parr = (int*)realloc(parr, 10 * sizeof(int));
	//上述代码合适吗
	//不合适,因为万一调整失败,会返回NULL
	//这时我原来开辟的空间就找不到了

	//所以正确的方法应该如下
	//先申请调整空间
	int* ptr = (int*)realloc(parr, 10 * sizeof(int));
	//调整成功
	if (ptr != NULL)
	{
		//将申请的空间地址赋给parr(如果是情况二,parr的原空间已经被释放了)
		parr = ptr;
	}
	else
	{
		perror("realloc");
	}
	//处理要求……

	free(parr);
	return 0;
}

上述代码int* parr = (int*)realloc(parr, 10 * sizeof(int));代码合适吗?
完全不合适!!! 因为万一调整失败,会返回NULL;
这时我原来开辟的空间就找不到了。
所以正确的realloc使用方式是先创建一个指针变量来接收开辟的空间,如果判断ptr是否开辟成功,如果开辟成功。将ptr赋给parr

如果开辟失败parr指向的原空间也能继续使用。

补充:realloc也可以完成malloc的功能;
如果传的是NULL指针,realloc就会开辟一块新空间。

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

3.1对NULL指针的解引用

具体如下:

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

int main()
{
	int* ptr = (int*)malloc(INT_MAX);

	*ptr = 20;//如果ptr是NULL,就会有问题

	return 0;
}

在这里插入图片描述
解决方法:开辟空间后,要第一时间判断是否空

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

具体如下:

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

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);
}

int main()
{
	test();

	return 0;
}

解决方法:控制好访问的范围

3.3对非动态开辟空间使用free释放

具体如下:

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

int main()
{
	int a = 0;
	int* p = &a;
	//处理……
	free(p);
	//这样行吗,肯定不行!!!
	//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的
	//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)
	return 0;
}
//这样行吗,肯定不行!!!
//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的
//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)

在这里插入图片描述
编译都无法编译过去

解决方法:不要对非动态开辟空间进行释放!!!

3.4使用free释放的并不是动态开辟空间的起始位置

具体如下:

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

int main()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
	return 0;
}

在这里插入图片描述
同样也无法编译过去
解决方法:传给free的时候确保是空间的起始地址

3.5对同一块内存进行对此释放

具体如下:

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

int main()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);
	return 0;
}

在这里插入图片描述
同样还是无法编译过去

解决方法:在第一次释放后及时的给p赋NULL

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

int main()
{
	int* p = (int*)malloc(100);
	free(p);
	//解决方法
	p = NULL;

	free(p);
	return 0;
}

前面也说过了,如果传给free函数的是NULLfree不会进行如何的操作。

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

具体如下:

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();//出来后test就被销毁了,就无法找到p所指向的空间了
	 
	while (1);
}

忘记释放不再使用的空间会造成内存泄漏
释放动态内存有两个方法:

  1. 在不用的时候使用free函数进行释放

  2. 如果一直没有被free释放,当程序运行结束后,会由操作系统来回收
    解决方法:
    1.谁申请的空间谁释放,如果在函数里,那么在出函数前记得释放malloc/calloc/realloc要和free成对出现。

    2.如果不能释放(后续会用到),要告诉下一个使用者,记得释放动态开辟空间。

切记:动态开辟的空间⼀定要释放,并且正确释放。

结语

最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

格雷希尔E10系列大电流测试连接器,在新能源汽车大电流接插件的电气测试方案

在新能源汽车的电驱动、电池包等设备的电测试处理中&#xff0c;格雷希尔E10系列电测试连接器具有显著的优势。E10系列的核心设计——插孔/插针&#xff0c;可以达到实验室10万次的插拔寿命&#xff0c;相比传统公母电接头500次左右的连接寿命&#xff0c;E10系列无疑大大减少测…

Golang错误处理机制

文章目录 Golang错误处理机制panic异常recover捕获异常自定义错误 Golang错误处理机制 panic异常 panic异常 Go的类型系统会在编译时捕获很多错误&#xff0c;但有些错误只能在运行时检查&#xff0c;比如除零错误、数组访问越界、空指针引用等&#xff0c;这些运行时错误会引…

实验15 MVC

二、实验项目内容&#xff08;实验题目&#xff09; 编写代码&#xff0c;掌握MVC的用法。 三、源代码以及执行结果截图&#xff1a; inputMenu.jsp&#xff1a; <% page contentType"text/html" %> <% page pageEncoding "utf-8" %> &…

day15 学一下Tailwindcss(java转ts全栈/3r教室)

目前距离全栈差得最多的是前端&#xff0c;而对于前端主要是CSS一直不熟悉&#xff0c;觉得很复杂写起来总是不上道&#xff0c;所以特别关注下Tailwindcss吧&#xff0c;其他前端框架可以先放放&#xff0c;多说无益直接用tailwindcss做个页面试试 看下文档&#xff1a;Tailwi…

【统计推断】-01 抽样原理之(四):中心极限定律

文章目录 一、说明二、样本均值的抽样分布三、两个重要公理四、中心极限定理4.1 定义4.2 中心极限定理的特点4.3 中心极限定理的条件 五、一个举例5.1 一个连续分布示例5.2 样本容量变化的对比 六、结论 关键词&#xff1a;    Central Limit Theorem    Law of Large Numb…

linux部署java1.8(java17)

两种方式&#xff1a; 方式一 1.输入查找命令&#xff1a; yum -y list java*2.输入安装命令&#xff1a; yum install -y java-1.8.0-openjdk.x86_643.测试是否已经安装&#xff1a; java -version方式二&#xff1a; 点击链接进入官网&#xff1a;https://www.oracle.com/…

mysql-sql练习-5-行列互转

目录 成绩单 简单互转 需求 多行转多列 分组 判断 聚合 理解 分组 合并 逆向需求 多列转多行 输出 合并 abc 去重 合并 拆分 需求 建表 多行转多列 逆向需求 多列转多行 拆分 按长度 拆分 按个数 成绩单 简单互转 需求 多行转多列 分组 判断 聚合 with tmp as(--…

3.电源模块趋旺盛,铁路最需可靠性

电源模块趋旺盛&#xff0c;铁路最需可靠性 电源设计需要很高的专业技能。越来越多的电子设备制造商开始采用电源模块来加快设计周期。通信、铁路、电力和军工领域&#xff0c;对电源模块需求越来越旺盛。 通信网络基建设备市场潜力巨大。应市场要求&#xff0c;现代的通信系…

自动化工具:推广神器,精准获客新策略

在当今这个信息爆炸的时代&#xff0c;推广和获客对于企业的生存和发展至关重要。然而&#xff0c;传统的推广方式不仅耗时耗力&#xff0c;而且效果往往难以精准把控。此时&#xff0c;自动化工具的出现无疑为市场推广带来了新的生机。本文将以客观公正的态度探讨如何利用自动…

[软件工具]批量根据文件名查找PDF文件复制到指定的地方,如何批量查找文件复制,多个文件一起查找复制

多个文件目录下有多个PDF, 如何根据文件名一个清单&#xff0c;一次性查找多个PDF复制保存 如图所示下面有7个文件夹&#xff0c;每个文件夹里面有几百上千PDF文件 如何从上千个PDF文件中一次性快速找到我们要的文件呢 &#xff1f; 我们需要找到文件名是这样的PDF&#xff0…

oracle pl/sql 如何让sql windows 显示行号

oracle pl/sql 如何让sql windows 显示行号 下载最新版的pl/sql第一步&#xff0c;在preferences中对sql Windows进行设置&#xff0c;如下所示第二步&#xff0c;在preferences中对User interface进行设置&#xff0c;如下所示结果如下 其实很简单 下载最新版的pl/sql 官方下…

【LangChain系列 12】Prompt模版——序列化

本文速读&#xff1a; PromptTemplate FewShotPromptTemplate 通常prompt以文件形式存储比python代码更好&#xff0c;一方面可以更容易共享、存储。本文将介绍在LangChain中如何对prompt以不同的方式序列化。 一般来说&#xff0c;对于序列化有以下两个设计原则&#xff1a…

深度学习系列64:数字人wav2lip详解

1. 整体流程 第一步&#xff0c;加载视频/图片和音频/tts。用melspectrogram将wav文件拆分成mel_chunks。 第二步&#xff0c;调用face_detect模型&#xff0c;给出人脸检测结果&#xff08;可以改造成从文件中读取&#xff09;&#xff0c;包装成4个数组batch&#xff1a;img…

74、堆-数组中的第K个最大元素

思路&#xff1a; 直接排序是可以的&#xff0c;但是时间复杂度不符合。可以使用优先队列&#xff0c;代码如下&#xff1a; class Solution {public int findKthLargest(int[] nums, int k) {if (numsnull||nums.length0||k<0||k>nums.length){return Integer.MAX_VAL…

神之浩劫2测试资格100%获取教程 测试资格获取方法教程

《神之浩劫》是一款基于Unreal 3&#xff08;虚幻3&#xff09;游戏引擎开发的3D团队竞技游戏&#xff0c;由美国Hi-Rez工作室开发、腾讯全球代理。2013年10月31日&#xff0c;游戏开启国服首测&#xff0c;并于2014年3月25日在美国公测。2018年1月20日&#xff0c;国服并入全球…

shell脚本-监控系统内存和磁盘容量

监控内存和磁盘容量除了可以使用zabbix监控工具来监控&#xff0c;还可以通过编写Shell脚本来监控。 #! /bin/bash #此脚本用于监控内存和磁盘容量&#xff0c;内存小于500MB且磁盘容量小于1000MB时报警#提取根分区剩余空间 disk_size$(df / | awk /\//{print $4})#提取内存剩…

Redis(七) zset有序集合类型

文章目录 前言命令ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZPOPMAXZPOPMIN两个阻塞版本的POP命令BZPOPMAX BZPOPMINZRANKZREVRANKZSCOREZREMZREMRANGEBYRANKZREMRANGEBYSCOREZINCRBY集合间操作ZINTERSTOREZUNIONSTORE 命令小结 内部编码使用场景 前言 对于有序集合这个名…

Java核心技术.卷I-上-笔记

目录 面向对象程序设计 使用命令行工具简单的编译源码 数据类型 StringBuilder 数组 对象与类 理解方法调用 继承 代理 异常 断言 日志 面向对象程序设计 面向对象的程序是由对象组成的&#xff0c;每个对象包含对用户公开的特定功能部分和隐藏的实现部分从根本上…

高校宿舍管理

在高等教育的迅猛发展浪潮中&#xff0c;大学校园正经历着前所未有的变革。随着招生规模的不断扩大&#xff0c;学生宿舍管理工作变得日益繁重和复杂。传统的管理方法&#xff0c;如使用Word和Excel进行数据记录和整理&#xff0c;已经无法满足现代高效、精准的管理需求。此外&…

关于几个水表术语的理解

GB/T778.1-2018《饮用冷水水表和热水水表 第 1 部分&#xff1a;量值要求和技术要求》、JJG162-2019《饮 用冷水水表检定规程》和 JJF1777-2019《饮用冷 水水表型式评价大纲》不仅规范了水表行业的专业名词解释&#xff0c;而且给出了影响水表性能的主要因素的定义。本文从影响…