相关推荐recommended
C语言进阶——动态内存管理(上)
作者:mmseoamin日期:2024-03-04

C语言进阶——动态内存管理(上),第1张

🌇个人主页:_麦麦_

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

                                                                                                          ——丰子恺《豁然开朗》

C语言进阶——动态内存管理(上),第2张

目录

​编辑

一、前言

二、正文

        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动态开辟内存忘记释放(内存泄漏)

 三、结语


一、前言

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

二、正文

        1.内存的分布

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

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

C语言进阶——动态内存管理(上),第4张 

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

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

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

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

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

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

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

        3.动态内存函数的介绍

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

                 3.1 malloc

void* malloc(size_t size);

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

●引用的头文件:

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

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

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

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

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

//malloc函数使用示例
#include 
#include 
#include 
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;
}

C语言进阶——动态内存管理(上),第5张 

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

                 3.2free

void free (void * ptr);

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

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

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

#include 
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;
}

C语言进阶——动态内存管理(上),第6张 

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

//malloc函数使用示例
#include 
#include 
#include 
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 
int main()
{
	int* p = calloc(10, sizeof(int));
	if (NULL!=p)
	{
		//使用空间
	}
	free(p);
	p = NULL;
}

C语言进阶——动态内存管理(上),第7张 

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

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

●calloc会自动初始化为0

     

                 3.4realloc

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

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

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

        下面具体来认识下realloc函数

●ptr是要调整的内存地址

●size为调整之后新的大小

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

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

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

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

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

C语言进阶——动态内存管理(上),第8张 

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

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

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

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

#include 
#include 
#include 
//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博客-领域博主

         大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!

C语言进阶——动态内存管理(上),第9张