C动态内存分配

对比一般内存分配和动态内存分配

一般内存分配

int val = 20;
char arr[10] = {0};

特点:
①大小是固定的,一旦分配好就无法改变(数组必须指定大小后编译器才能分配空间)
②分配的空间具体放置什么类型的数据是在分配内存时确定好的

动态内存分配

有时候数组需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了,这时候就只能试试动态存开辟了。

动态内存函数

一.malloc和free

☀️1.malloc函数功能参数介绍

C动态内存分配_第1张图片
功能:申请一块空间
参数:申请空间的字节数
返回值:void ∗ * 类型的。申请成功,返回申请空间的起始位置;申请失败,返回NULL
申请到的空间在内存中的位置:
C动态内存分配_第2张图片

malloc使用时的注意事项

①申请到的空间是没有类型的,因此返回值是void ∗ * 类型的,需要强制转换为想要的类型
②不会初始化空间内容
③参数不可以是0,即不可以申请0字节的空间,这种行为是标准未定义的
④malloc只负责申请,不会将空间还给系统,除非程序结束,否则空间不会归还,因此需要free函数释放空间

☀️2.free函数功能参数介绍

C动态内存分配_第3张图片
①参数:与free配合使用的某个动态内存分配函数所申请空间的起始位置
②无返回值
(free是如何做到精准释放的呢?其实申请了不止指定大小个空间,在这些空间之前还多申请了一部分空间来存放该空间大小,通过该指示信息从而达到精准释放空间,这些在底层实现,不会暴露出来)

free使用时的注意事项

①如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
例如下图错误操作:
C动态内存分配_第4张图片

②如果参数 ptr 是NULL指针,则函数什么事都不做。
③假设p指向通过malloc动态开辟的空间,当空间被free释放后,p就成了野指针,因此free完后还要将p指针置为空

☀️3.malloc函数使用模版

#include
int main()
{
	//步骤一:申请空间
	int* p = (int*)malloc(40);
	//步骤二:判断是否开辟成功(返回值是否为空指针)
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//开辟成功
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		printf("%d\n", *(p + i));
	}

	//归还空间,并将指针置为空
	free(p);
	p = NULL;

	return 0;
}

步骤一:申请空间
步骤二:判断是否开辟成功(返回值是否为空指针)
步骤三:开辟成功进行操作
最后:归还空间,并将指针置为空
运行结果:
C动态内存分配_第5张图片
说明malloc不会初始化空间

malloc开辟空间失败(比如要开辟的空间太大)

将要开辟的空间改为INT_MAX(大约21亿)

int* p = (int*)malloc(INT_MAX);

运行结果:
C动态内存分配_第6张图片

二.calloc和free

☀️1.calloc函数功能参数介绍

C动态内存分配_第7张图片
功能:在申请空间的基础上将申请到的每个字节都初始化为0
C动态内存分配_第8张图片

参数一:开辟多少个空间num
参数二:每个开辟的空间的大小size
num*size才是最终申请并初始化的字节数
返回值:和malloc一样

☀️2.calloc函数使用模版

#include
int main()
{
	//步骤一:申请空间
	int* p = (int*)calloc(10,4);
	//步骤二:判断是否开辟成功(返回值是否为空指针)
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//开辟成功
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		printf("%d\n", *(p + i));
	}

	//归还空间,并将指针置为空
	free(p);
	p = NULL;

	return 0;
}

运行结果:
C动态内存分配_第9张图片
说明calloc会初始化空间为0

calloc开辟空间失败(比如要开辟的空间太大)

将要开辟的空间改为INT_MAX(大约21亿)

int* p = (int*)calloc(INT_MAX,sizeof(int));

运行结果:
C动态内存分配_第10张图片

三.realloc

☀️1.realloc函数功能参数介绍

C动态内存分配_第11张图片
功能:有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的利用内存,可以用realloc函数对内存的大小做灵活的调整。
参数一:过去所申请空间的起始位置
参数二:想要调整成为多大的空间(注:不是空间扩大的量,而是扩大后空间整体的大小)
返回值:void ∗ * 类型的,具体有两种情况
①返回值是被调整之前的空间起始地址:把空间往小了调整,或往大了调整但调整前空间后面有足够大的位置能够连续存放下这个大空间,此时调整后的空间起始地址和调整前的一样
C动态内存分配_第12张图片

②返回值是一块新空间的起始地址:(只有往大了调整才会出现这种情况)调整前的小空间后面没有足够大的位置能够连续存放下大空间,系统只能舍弃当下的这个小空间,另找一处大空间将小空间的内容以及增加的空间一起连续存放。简单来说就是将旧空间释放掉,返回新空间的地址。
C动态内存分配_第13张图片

③基于②,当怎么找也找不到一块新的连续空间时,会失败,返回空指针
当参数一ptr传的是NULL时,此时realloc的功能和malloc一样

realloc使用时的注意事项

不可以让p指针(p指针是原先监管由malloc或calloc申请的空间的指针)直接接收realloc的返回值,因为当realloc寻找新空间失败并返回空指针时,p也编变成空指针,此时不但没有得到新空间的地址,连原先malloc或calloc申请到的空间也监管不到了,此时之前由malloc申请到的空间处于即用不到又找不到的状态。该状态就叫内存泄漏。

☀️2.正确使用realloc的模版

用一个新的指针ptr接收realloc的返回值,当返回值不为NULL时再将ptr的值赋给原空间起始地址p

#include
int main()
{
	//步骤一:申请空间
	int* p = (int*)malloc(40);
	//步骤二:判断是否开辟成功(返回值是否为空指针)
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//增加一些空间
	int* ptr =(int*)realloc(p, 80);
	if (ptr != NULL)
	{
		p = ptr;
	}
	//增容成功
	int i = 0;
	for (i = 0;i < 20;i++)
	{
		printf("%d\n", *(p + i));
	}

	//归还空间,并将指针置为空
	free(p);
	p = NULL;

	return 0;
}

运行结果:
C动态内存分配_第14张图片

四.总结

①所有动态内存分配函数都在stdlib.h头文件中
②所有动态内存分配函数分配到的空间都在堆区上
③接收所有动态内存分配函数的返回值都需强制转换
④动态申请的空间不会因为出作用域而自动销毁(返还给操作系统),只有两种方式销毁,即用free释放或程序退出

常见动态内存错误

一.对NULL解引用

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}

出现场景:malloc或calloc申请空间失败,p就是空指针了,此时不可以解引用p并赋值
解决方法:先判断指针是否为NULL,不为空再释放

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

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

申请了20个字节的空间,但访问了20*4=80个字节
注:malloc和calloc表示空间大小的参数是以字节为单位的

三.释放一块动态开辟内存的一部分

C动态内存分配_第15张图片

此时p已不再指向动态开辟空间的其实位置

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

void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
}
int main()
{
 test();
 while(1);
}

在test函数中开辟了100字节的空间,但没有释放,出函数时指针变量p销毁了,但空间以及空间内的内容还存在,此时没有办法可以访问到这部分空间,造成了空间的流失(内存泄漏),只有退出程序后该部分空间才会返还给系统。
程序在执行完malloc、calloc或realloc后,但还没到free时提前跳出,此时也会造成内存泄漏。
遇到电脑内存逐渐减少,电脑崩了,重启后又好了,这种情况很可能是内存泄漏。

五.对非动态开辟内存使用free释放,

C动态内存分配_第16张图片

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

C动态内存分配_第17张图片

动态内存相关笔试题

题目一:

请问运行Test 函数会有什么样的结果?

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

1.运行结果:由于访问空指针而引发异常
C动态内存分配_第18张图片
2.错误分析:Getmemory传参穿的是str本身,而不是地址,不是传址调用,虽然p里最终放的是malloc申请到100个空间的起始位置,但str里放的还是NULL,strcpy不可以直接对空指针str解引用,最终程序会出错,也无法打印出hello world。没有释放由malloc申请的空间。
3.正确写法
①传址,即传&str,并用二级指针接收参数
②要释放空间并将指向空间的指针str置为空
C动态内存分配_第19张图片
注:printf(str)这样写是对的。公认正确的打印方式是printf(“hello world”),但本质上是将字符串"hello world"的首元素地址传给了printf函数。当指针变量str存放了了字符串的首元素地址后,将str传给printf函数也可以打印出该指针指向的字符串。

题目二:

请问运行Test 函数会有什么样的结果?

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

1.运行结果:C动态内存分配_第20张图片
2.错误分析:p指向的空间出GetMemory函数就被销毁了,虽然str存放了p的值,但通过该地址找不到字符串对应的空间,此时str变量里存放的指针是野指针,顺着野指针访问到的空间内容当然看不懂。该问题属于返回栈空间地址问题
3.正确写法:
C动态内存分配_第21张图片
在GetMemory函数中的字符串p前加上static,使字符串作用周期变长,此时就可以通过指针访问到字符串并打印出字符串了

4.类似错误
C动态内存分配_第22张图片
也属于返回栈空间地址问题,解引用指针p得不到变量a的值,因为出函数时a就被销毁了。

题目三:

请问运行Test 函数会有什么样的结果?

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

1.运行结果:可以打印出"hello"
2.错误分析:有内存泄漏问题。没有释放malloc申请的空间

题目四:

请问运行Test 函数会有什么样的结果?

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

1.错误分析:虽然打印出了"world",但由于str指向的空间已经还给操作系统了,此处就是非法访问,程序有可能崩
2.正确写法:free释放完后立马将指针置为空,这样即使后面有关于str的操作,也不会产生什么坏影响

❤️感谢浏览,如有错误请及时指正哦!

你可能感兴趣的:(c语言,开发语言)