C语言之动态内存分配一

导读:本篇文章讲解 C语言之动态内存分配一,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

为什么存在动态内存分配?:

在这里插入图片描述

但是这两种方式在某些情况下是不适用的,假设现在我们要在结构体中存放学生的信息。

struct S
{
	char name[20];
	int age;
};
int main()
{
	struct S arr[50];
	return 0;
}

但很明显,学生的数量是不确定的,如果我们分配内存为50个学生的大小,当学生人数超过这个数量就会出现存放不下的情况,那么有的人会说,我们可以开辟足够大的空间,这种方式看似解决了这个问题,但是会出现内存浪费的情况,假如说你申请了1000个内存空间,结果你只用了10个或者100个,这就浪费了大量的内存空间,那么我们是否可以将arr[]中的元素值先设置为n,再通过scanf函数输入n的值?
答案是:C语言不支持,但C99支持,要想创建变长数组,gcc是支持这种操作的,而我们使用的VS编译器并不支持这种操作。

什么叫变长数组?

数据的长度可以任意变化。

举例:

int main()
{
	int n;
	scanf("%d", n);
	struct S arr[n];//数组的长度并不是不变的
	return 0;
}

由此,C语言给出了一种动态内存分配。

动态内存分配的介绍:

malloc和free:头文件:#include<stdlib.h>

C语言提供了一个动态内存开辟的函数:

void* malloc(size_t size);

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

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

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

返回值的类型是void ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自行决定。

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

申请空间:

*void*malloc(sizeof(ELemType)num)

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	//向内存申请10个整形空间
	int *p=(int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		//打印错误原因
		printf("%s\n", strerror(errno));
	}
	else
		//正常使用空间
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	return 0;
}

输出如下所示:

0 1 2 3 4 5 6 7 8 9 

释放空间:

free函数用来释放动态开辟的内存:

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

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

当动态申请的空间不在使用的时候,就应该还给操作系统:

方法:

free(p);

将该语句加入到上面的代码中,再运行!

输出结果为:

0 1 2 3 4 5 6  7 8 9

输出这个结果难免会让我们产生疑惑,好像释放空间和不释放没有任何区别,其实事实并非如此。

上述代码即使没有加free,其内存空间也会被释放,其原因是当程序的生命周期结束时,动态申请的内存空间会自动还给操作系统,但我们并不提倡这种做法,原因是:上述代码,恰好在free语句下面就执行完了所有的程序,因此加不加free的效果是相同的,但是,如果free后面还有其他的逻辑语句,就会导致上面不用的代码一直占着内存,因此在以后的代码中,我们一定不能忘记加free.

但是这种释放方式还不够彻底,p的内存空间只是被释放了,但是它的值并没有改变,要想彻底使p释放,我们应在释放内存空间的语句后面再将p指向NULL,这才会彻底释放。

calloc函数:

calloc函数也可以用来动态内存分配;

void*calloc(size_t num,size_t size);

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

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

举例:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	//malloc(10*sizeof(int));
	int* p=(int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("%s\n",strerror(errno));
	}
	else
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	free(p);//释放动态开辟的空间
	p = NULL;
	return 0;
}

输出如下:

0 0 0 0 0 0 0 0 0 0

因此如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

realloc函数:

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

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间太大了,那为了合理的使用内存,我们必须要对内存的大小做灵活的调整,那么realloc函数就可以做到对动态开辟内存大小的调整,函数原型如下:

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

ptr:要调整的内存地址

size:调整之后新的大小

返回值为调整之后内存起始位置

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

realloc在调整内存空间的是存在两种情况:
在这里插入图片描述

情况一:原有空间之后有足够大的空间,不需要开辟新的空间,直接进行追加

原有空间的数据不发生变化。

情况二:原有空间之后没有足够大的空间,需要开辟新的空间

举例:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("开辟失败");
	}
	else
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			*(p + i) = i;
		}
	}
	//上述所使用的空间为我们在程序前面通过malloc函数申请的20个空间
	//假设此时该空间不够用了,我们还需要20个字节的空间,此时我们可以使用realloc来调整动态内存空间的大小
	int*ptr=(int*)realloc(p, 40);
	if (ptr != NULL)//追加成功
	{
		p = ptr;
		int i = 0;
		for (i = 5; i < 10; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	free(p);//如果返回的是新地址,新地址需要用free函数释放,而旧地址会被realloc函数释放
	p = NULL;
	return 0;
}

常见的动态内存错误:

对NULL指针进行解引用操作:

int main()
{
	int* p = (int*)malloc(40);
	int i = 0;
	//如果malloc没能正确进行,p就被赋值为NULL,此后进行++操作为非法操作
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
	return 0;
}

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

int main()
{
	int* p = (int*)malloc(5*(sizeof(int)));
	if (p == NULL)
	{
		return 0;
	}
	else
	{
		int i = 0;
		for (i = 0; i < 10; i++)//上面只开辟了5个整形元素的空间,这里i<10;为越界访问
		{
			*(p + i) = i;
		}
	}
	free(p);
	p = NULL;
	return 0;
}

对非动态开辟内存的free:

int main()
{
	//p并不是存在堆区的,free不能对齐进行访问
	int a = 10;
	int* p = &a;
	*p = 20;
	free(p);
	p = NULL;
	return 0;
}

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

#include<string.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 0;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p++ = i;//p的指向位置已经发生改变,已经不是动态开辟的内存的起始位置
	}
	free(p);
	p = NULL;
	return 0;
}

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 0;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p++ = i;//p的指向位置已经发生改变
	}
	free(p);
	//...
	free(p);//多次释放p
	p = NULL;
	return 0;
}

编译程序崩溃,原因是我们对p进行多次释放。
在这里插入图片描述那么如何避免这种错误呢?
方法:释放完内存,立即将其指向NULL,上面我们提到过,如果指针指向的是NULL,再对其进行释放,编译器将不会做任何事情。

free(p);
p = NULL;
//...
free(p);

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

int main()
{
	while (1)
	{
		malloc(1);//不断的开辟空间,但不进行释放
	}
	return 0;
}

将程序运行后,打开资源管理器,我们会发现,内存的占用比一直在增加。
在这里插入图片描述忘记释放不再使用的开辟的空间会造成内存泄漏。
切记:动态开辟的空间一定要释放,并且正确释放。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/81459.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!