【C语言】动态内存管理:malloc、calloc、realloc、free

news/2024/9/28 19:31:34 标签: c语言, 开发语言

 本篇介绍一下C语言中的malloc/calloc/realloc。 使用这些函数需要包含头文件<stdlib.h>。malloc/calloc/realloc申请的空间都是 堆区的。

1.malloc和free

1.1 malloc

C语言提供了一个动态内存开辟的函数malloc,函数原型如下。

void* malloc(size_t size); //size的单位是字节

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

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

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

返回值类型是void*,所以malloc函数并不知道开辟空间的类型,具体要在使用的时候自己来决定。要强制类型转换。

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

 比如我们申请20个字节,用来存放整形。下面两种写法是一样的。

int* p0 = (int*)malloc(20);
int* p1 = (int*)malloc(5 * sizeof(int));

然后检查一下返回值。

int* p0 = (int*)malloc(20);
//检查返回值
if (p0 == NULL) //申请失败
{
	perror("malloc fail"); 
	return 1;
}

申请成功就可以使用空间了。当作数组使用就行。

 然后我们往里面存一些值进去

1.2 free

申请的空间不要了,还要手动换回去。C语言提供了另一个函数free,专门是用来做动态内存释放和回收的,函数原型如下。

void free(void* ptr);

free函数用来释放动态开辟内存。ptr传的是要释放的内存空间的起始地址

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

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

拿前面的p0举例,我们现在要释放它。

free(p0); //把空间还给操作系统

 

此时p0指针什么都没指,是个野指针,所以我们还要手动把p0置为NULL,这一步千万不能忘。

free(p0); //把空间还给操作系统
p0 = NULL; //置空

 所以我们在使用申请的空间的时候,要记得记录起始位置指针,最好就是别改变起始位置指针的指向。

2.calloc和realloc

2.1 calloc

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

void* calloc(size_t num, size_t size);

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

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

比如我们想向内存申请5个整形的空间。

int* p1 = (int*)calloc(5, 4);
int* p1 = (int*)calloc(5, sizeof(int)); //这样写也可以

同样的,要检查返回值。

int* p1 = (int*)calloc(5, sizeof(int));
if (p1 == NULL)
{
	perror("calloc fail");
	return 1;
}

我们通过调试看p的内存,发现都初始化为了0。 

 除了calloc能初始化,其他的和malloc没什么区别。

最后也是一样,不用了就free,并且把p1置空。

free(p1);
p1 = NULL;

2.2 realloc

realloc的出现让动态内存管理更加灵活,它可以调整动态开辟内存的大小,函数原型如下。

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

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

比如说我要把前面的p0申请的20个空间扩展到40个。有下面的两种情况。

情况一:直接在原来的基础上往后扩展就行,然后返回起始地址。 

情况二 :

        1.在内存里重新找一块区域,这块区域直接满足40个字节的需求。

        2.把原来的内容都拷贝在这个新内存里。

        3.释放旧空间。(realloc主动释放,不用自己手动释放)

        4.返回新的内存空间的起始地址。

情况三:新找的空间也不够,直接返回NULL。

所以我们在使用realloc的时候就不能直接像下面这样写。

int* p0 = (int*)malloc(20);
if (p0 == NULL) 
{
	perror("malloc fail");
	return 1;
}
for (int i = 0; i < 5; i++)
{
	*(p0 + i) = i;
}

int* p0 = (int*)realloc(p0, 40); //错误写法

因为一旦扩容失败,返回NULL,把NULL放在p0里,不但没有调整空间,原来的数据也丢失了。所以不能直接用原来的指针直接接受扩容后的新地址,要先进行一个判断,如下。

int* newptr = (int*)realloc(p0, 40);//新指针接收
if (newptr != NULL) //判断是否成功
{
	p0 = newptr; //成功了再用原来的指针接管扩容后的空间
}
else //失败
{
    perror("realloc fail"); 
	//...
}

 不用了同样要free释放。

realloc函数的第一个参数如果传NULL指针的话,可以实现和malloc一样的功能。

realloc(NULL, 20); //== malloc(20);

3.常见的动态内存错误

3.1 对NULL指针解引用

void test1()
{
	//INT_MAX是int最大值,此时空间开辟绝对是失败的
	int* p = (int*)malloc(INT_MAX); 
	*p = 20;  //开辟空间失败返回NULL,不能对NULL解引用
	free(p);
	p = NULL;
}

所以我们一定要判断malloc的返回值。 

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

void test2()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return;
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;  //当i为10时越界访问
	}
	free(p);
	p = NULL;
}

malloc申请的空间和数组非常相似,不可以越界访问。

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

void test3()
{
	int a = 10;
	int* p = &a;
	free(p);
    p = NULL;
}

不是动态开辟的空间不需要free,int的空间在栈上,也不在堆上,动态申请的才在堆区上。free只释放动态开辟的。

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

void test4()
{
	int* p = (int*)malloc(100);
	p++;
	free(p); //p此时不再指向起始位置
	p = NULL;
}

free(ptr)我们前面说过ptr必须是起始位置。只释放一部分程序会出错崩溃。

3.5 对同一块内存多次释放

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

这种情况最好避免发生的方法就是free之后赶紧置空。

void test5()
{
	int* p = malloc(100);
	free(p);
	p = NULL;
	free(p); //free空,什么都不发生
}

 

3.6 动态开辟的内存忘记释放

void test6()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test6();
	//出test6函数后p就销毁了,想释放现在也释放不了
	
	while (1);//程序死循环

	return 0;
}

自己已经不用了,不释放不还给操作系统,别人也不能用,会发生内存泄漏问题。

但是我们不用free释放,程序运行结束的时候,也会由操作系统回收,如果程序一直不退出呢?所以最好别忘记释放。

本篇分享就到这里,拜拜~


http://www.niftyadmin.cn/n/5681715.html

相关文章

汽车总线之---- LIN总线

Introduction LIN总线的简介&#xff0c;对于传统的这种点对点的连接方式&#xff0c;我们可以看到ECU相关的传感器和执行器是直接连接到ECU的&#xff0c;当传感器和执行器的数量较少时&#xff0c;这样的连接方式是能满足要求的&#xff0c;但是随着汽车电控功能数量的不断增…

代码为笔,合作作墨,共绘共赢画卷———未来之窗行业应用跨平台架构

合作共赢&#xff0c;代码同创&#xff0c;成就非凡 一、资源整合方面 1.1. 技术资源共享 - 不同的合作伙伴可能在技术领域各有所长。例如&#xff0c;一方可能擅长前端用户界面设计&#xff0c;具有丰富的交互设计经验&#xff0c;能够打造出美观、易用的预订界面&#xff…

linux 目录文件夹操作

目录 移动绝对目录&#xff1a; 移动相对目录 test.py报错&#xff1a; 移动绝对目录&#xff1a; mv -f /lpai/log /data data目录下会有一个目录log 移动相对目录 mv ./segmentation_results /lpai/volumes/ad-op-ga/code/ap_pose/AG-Pose-main/data test.py报错&#…

PaddleOCR 表格识别,docker部署,cpu版本

前置环境 centeros7 docker 拉取镜像 docker pull registry.baidubce.com/paddlepaddle/paddle:2.6.1 参考&#xff1a;开始使用_飞桨-源于产业实践的开源深度学习平台 这里拉取的镜像并不能立马用&#xff0c;只是内置好运行环境 随便找个目录下载paddleocr的代码 git…

Mybatis详细教程 (万字详解)

Mybatis 3.5.14 来自于B站‘天气预报’,一名宝藏up,跟着他可以培养起独立解决编程问题的能力&#xff01;&#xff01;&#xff01; 01.简介 1.1 官网 官方中文网: MyBatis中文网 中文网参考手册 1.2 概念 MyBatis 是一款优秀的持久层框架&#xff0c;支持自定义 SQL, 存储过…

后端架构师需要具备哪些能力

作为一名后端架构师&#xff0c;需要具备广泛的技能和能力&#xff0c;以确保系统架构的高效性、可扩展性、可靠性和安全性。以下是后端架构师应具备的关键能力&#xff1a; 1. 系统设计与架构 分布式系统设计&#xff1a;理解分布式架构的原理&#xff0c;包括如何设计和优化…

银河麒麟V10下如何将TXT文件转为PDF?

银河麒麟V10下如何将TXT文件转为PDF&#xff1f; 1. 安装软件2. TXT转PS3. PS转PDF &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在银河麒麟高级服务器操作系统V10中&#xff0c;将TXT文件转换为PDF文件可以通过简单的几步完成。 1. 安装…

一键连接安卓与电脑,尽享高效便捷的跨设备体验

LinkAndroid 或类似名称的软件确实提供了一种方便的方式来连接Android设备与电脑&#xff0c;从而实现了多种便捷功能&#xff0c;如投屏、文件传输、应用管理、截屏、录屏以及直接从电脑安装应用到手机等。这样的工具对于需要高效工作和娱乐的用户来说非常有用。 主要功能详解…