动态内存管理

本文详细介绍了动态内存分配的必要性,以及malloc,free,calloc,realloc的使用方法和注意事项,同时列出了动态内存管理中常见的错误类型,如野指针、内存越界、内存泄漏等,帮助读者理解和避免这些问题。
摘要由CSDN通过智能技术生成

很喜欢一部动漫,《星游记》,有这样一句话:“相信奇迹的人,本身和奇迹一样了不起!”

       来看一下我们这次要抵达的彼岸吧!

一 . 为什么要有动态内存的分配。

二 . malloc和free的使用。

三 . calloc和realloc的使用。

四 . 动态内存常见的错误。

五 . 动态内存的经典试题分析。


       正式开始之前我们先了解待会用到的内容!

1.动态内存开辟是在堆区进行操作!

2.malloc,calloc,realloc,所开辟的内存都是连续的!

3.动态内存开辟后释放的方式有哪些?

答:动态内存一但开辟好,释放方式就已注定,其一:程序结束后堆区开辟的内存由系统自动销毁其二:专门负责销毁堆区内存的库函数free来执行!

4.上面提到的销毁是什么?

答:需要使用内存的时候,我可以向操作系统的内存申请属于我的内存空间,当使用完后是要还给操作系统的,不能占为己有(操作系统说:你不用请归还,我还要给其他人用)这个就叫是销毁!所以销毁可不能理解字面意思将我们借到的内存给彻底删除呀!


       进入正题!

一 . 为什么要有动态内存的分配

先看俩条语句

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

       在没有学习动态内存管理前,开辟内存的方式有以上俩种,当程序运行后他们在栈区就完成了内存的开辟,并且无法做出对其大小的更改!这时就出现了内存管理上的问题 ,当使用静态数组写一个通讯录时,我们无法预测将来要存放信息量的大小,如果数组给大了则造成剩余空间的浪费,如果给小了则造成容量不足,所以C语言引入了动态内存开辟,可让使用者更高效更方便的对内存进行开辟和销毁 。

二 . malloc和free的使用。

 1 . malloc函数的使用

void* malloc (size_t size);

通过malloc函数在堆区内存中开辟一块连续的空间,大小是size个字节,最后返回malloc出来的内存空间的指针,因为所开辟的内存空间不可能是负数所以用size_t类型。

注意:

(1)当malloc在堆区成功开辟好一块内存空间,则返回这块空间的指针。

(2)当malloc在堆区开辟空间失败后,则返回NULL指针,所以要对返回值进行检测             防止形成野指针。

(3)malloc函数的返回值类型是任意类型的指针,所以要根据自己的需求对函数返回           值进行处理,以便达到自己的需求。

(4)malloc函数开辟好的空间是不进行初始化的。

(5)给malloc函数的形参传入0,是一种错误写法,既然你想开辟内存但你又开辟0字           节这不是没事找事吗,这时malloc属于标准未定义行为。

图1:展示了上面的五点注意事项!这时还没有对动态内存进行释放!

图一

2 . free函数的使用

void free (void* ptr);

        首先free函数是专门销毁动态开辟的内存,通过接收动态内存开辟的指针,来对其进行销毁!因为malloc,calloc,realloc的返回值都是任意类型的指针,要根据自己的需求对函数返回值进行处理,以便达到自己的需求,所以free函数的形参是任意类型的指针,以便接收任意类型的指针。

注意:

(1)free函数接收的指针必须是起始地址。

(2)free函数接收到NULL指针后,不做任何处理。

(3)free函数接收的指针必须是堆区的指针,若传入非堆区指针,则free标准为定义。

图2:有了free函数就可以对图1的代码进行完善,将有借有还体现出来!

图二

三 . calloc和realloc的使用。 

1 . calloc函数的使用

void* calloc (size_t num, size_t size);

        calloc函数在堆区开辟一块连续的内存空间,大小是num个size字节的内存空间,再将开辟好的内存空间的指针进行返回,这里值得留意的是,在返回前会将开辟好的内存空间初始化为0。

注意:

(1)malloc和calloc唯一的区别是:函数进行返回前calloc会将开辟好的内存空间初             始化为数值0。

(2)需要用到堆区内存初始化的情况就用calloc。下面演示calloc的使用。

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));//打印结果全是0,与malloc鲜明对比。
	}
	free(p);
	p = NULL;
	return 0;
}

 2 . realloc函数的使用(非常重要)

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

        realloc函数是将malloc,calloc,realloc在堆区开辟出来的内存进行调整,ptr指向的是被调整的地址,size是重新调整的大小,然后将调整后的内存的指针进行返回!

注意:

特殊情况:当ptr指向NULL时,realloc函数等价于malloc函数

(1)ptr指向的是被调整的对象,对象指的是malloc,calloc,realloc开辟的空间的             指针。

(2)size指的是重新开辟的大小(包括原有的大小)为什么会这样?

(3)返回调整后的指针。

(4)重要:realloc调整内存的俩种情况!  解答(2)中的疑问!

   情况一:要调整的对象后面有足够的空间。

图三

   情况二:要调整的对象后面没有足够的空间。

图四

(5)realloc不要使用接收calloc的指针变量,如果realloc正常返回那没问题,但如果           返回了NULL,则calloc开辟的内存就造成了内存泄漏。

以下代码将上面的5点注意都包含在内。

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

int main()
{
	int* p = (int*)calloc(10 , sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}

	int* ptr = realloc(p, 20 * sizeof(int));
//防止造成内存泄漏,所以我先用ptr接收realloc的地址,然后判断ptr有没有问题,若没有,再给到p进行操作

	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		p = ptr;
		ptr = NULL;
//因为我要用p指针代替ptr,防止将free函数将p释放后,再用ptr找回去,形成野指针。
	}
	for (int i = 0; i < 20; i++)
	{
		printf("%d\n", p[i]);
	}
	free(p);
	p = NULL;
//好的编程习惯是释放完记得将接收他地址的变量置为NULL。
	return 0;
}

        realloc函数的出现让我们对动态内存的管理更上一个台阶!假设用动态数组写一个通讯录,当容量等于元素个数时,就在堆区开辟原有内存的二倍,如果原有1000个元素满了而我又只需再添加一个那么剩余的999个内存是不是浪费了,那我们就要用到realloc函数来进行内存销毁,肯定有人会想,那我用free呀!那请问free能接收非起始地址的指针吗!!!再想,如果原有1000个元素满了而我又只需再添加一个,那我可以直接使用realloc再开辟一个内存即可,所以有了realloc后,我们可以通过realloc函数去调整堆区内存的大小。

四 . 动态内存常见的错误

(1)对NULL指针解引用操作!

        这里ULLONG_MAX超出了堆区的内存范围,malloc是无法开辟内存的所以返回了NULL,紧接着对NULL解引用就会造成野指针 (使用ULLONG_MAX)需要包含头文件 #include<limits.h> 

int main()
{
	int* p = (int*)malloc(ULLONG_MAX * sizeof(int));
	if (p == NULL)
	{
		*p = 10;
	}
	//这里ULLONG_MAX超出了堆区的范围malloc是无法开辟内存的所以返回了NULL,
    //紧接着对NULL解引用就会造成野指针。
	return 0;
}

   (2)对动态开辟的内存越界访问!

        堆区只开辟了40字节,而我却要以整型的方式去访问第11个以及往后的整型,
就造成了堆区开辟内存的越界非法访问。

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 16; i++)
	{
		printf("%d", p[i]);
	}
	//堆区只开辟了40字节,而我却要以整型的方式去访问第11个以及往后的整型元素,
	//就造成了堆区开辟的内存越界非法访问
	return 0;
}

(3)对非动态开辟的内存使用free访问!

         free是专门用来销毁动态开辟出来的内存,否则造成free标准未定义。

int main()
{
	int a = 10;
	int* p = &a;
	free(p);
	//free是专门用来销毁动态开辟出来的内存。
	return 0;
}

(4)使用free释放一块动态开辟内存的一部分!

         释放动态开辟内存只能从头释放!

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 3; i++)
	{
		p++;
	}
	free(p);
	//释放动态开辟内存只能从头释放!
	return 0;
}

(5)对同一块动态内存多次释放!

        这里主要考察,free后一定要记住,置NULL接收动态内存地址的指针变量,这样就没有后面free的事了,因为给free传入NULL,free函数不做任何处理。

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
	    return 1;
    }
	free(p);
	free(p);
	//这里主要考察,free后一定要记住,置NULL接收动态内存地址的指针变量,这样就没有后面free的事了。
	return 0;
}

(6)动态开辟内存无法释放造成内存泄漏!

        realloc开辟失败返回NULL,导致calloc的地址丢失,自己占用,却自己不能用,同时别人还用不上,造成内存泄漏。

int main()
{
	int* p = (int*)calloc(10 , sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int* p = realloc(p, ULLONG_MAX * sizeof(int));
	//realloc开辟失败返回NULL,导致calloc的地址丢失,造成自己不能用的同时别人也不能用,造成内存泄漏
	return 0;
}

(6)动态开辟内存忘记释放造成内存泄漏!

         test函数调用完成后,p除了作用域被销毁,造成malloc的内存空间无法找到。

void test()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
	    return 1;
    }
	else
	{
		*p = 66;
	}
}

int main()
{
	test();
	//test函数调用完成后,p出了作用域被销毁,造成malloc的内存空间无法找到导致无法销毁,也可能是忘记销毁
	return 0;
}

 再次注意上面六条代码是错误警告!!!要避免的!!!

不仅一番彻骨寒,怎得梅花扑鼻香。

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值