论动态内存的正确开辟

前言

使用动态内存开辟可以让我们对内存空间的使用更加灵活,但如果使用不当就可能让整个程序崩溃。下面通过4道经典面试题来测试一下你以前使用动态内存的方法是否正确,答案在文末公布,如果每道题你都能讲出个所以然来,恳请指出我有没有什么讲得不到位的地方。

4道经典面试题

请问以下4段代码运行Test函数后分别有什么结果

(一)
void CreatSpace(char*p)
{
	p=(char*)malloc(100);
}
void Test()
{
	char*str=NULL;
	CreatSpace(str);
	strcpy(str,"hello world");
	printf(str);
}
()
char *CreatSpace(void)
{
	char p[]="hello world";
	return p;
}
void Test(test)
{
	char*str=NULL;
	str=CreatSpace();
	printf(str);
}

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

动态内存函数的介绍

malloc

函数原型

void* malloc(size_t size);

malloc函数是向内存申请一块连续可用的空间,并返回这块空间地址

· 如果空间开辟失败会返回一个空指针,所以malloc的返回值一定要做检查
· 返回的指针类型是void型,使用者要根据自己需求进行强制类型转换
· 如果size为0,malloc的行为是未定义的,取决于编译器

free

函数原型

void free(void *ptr)

free函数用来释放和回收动态开辟的内存
· 如果ptr指向的空间不是动态开辟的,则free函数的行为是未定义的
· 如果ptr是空指针,则函数什么都不做

calloc

函数原型

void* calloc(size_t num,size_t size);

calloc函数用于为num个大小为size的元素开辟空间

· 如果空间开辟失败会返回一个空指针,所以calloc的返回值也要做检查
· 返回的指针类型是void型,使用者要根据自己需求进行强制类型转换
·会把申请的空间的每个字节初始化为0,这是与malloc函数的区别

realloc

函数原型

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

realloc函数用于将ptr指向的空间调整为size个字节
· 返回的指针类型是void型,使用者要根据自己需求进行强制类型转换
· 空间调整时存在3种情况:
①原有空间后有足够大的空间
则拓展的内存直接就在原有内存之后追加空间,原有数据不发生变化

②原有空间之后没有足够的空间
在堆区再寻找一块适合大小的连续空间来使用,并返回这块新空间的地址,同时会把原来空间的数据拷贝过来,并释放原有空间

③找不到足够大的空间,返回空指针,因此realloc的返回值也要做检查

现在给你一段代码,看看有什么缺陷,当然了,你最好在看完后面6种常见的动态内存错误后再返回来看这段代码

int main(void)
{
	int*ptr=(int*)malloc(100);
	if(ptr!=NULL)
	{
		//业务处理
	}
	else
	{
		return 0;
	}
	
	//现在要拓展空间
	ptr=(int*)realloc(ptr,1000);
	//业务处理
	free(ptr);
	ptr=NULL;
	return 0;
}

这段代码的缺陷在于:如果空间拓展失败,返回了空指针,则ptr被置为NULL,导致我们找不到原来malloc函数开辟的空间了,但我们原来的空间还没有释放掉,从而造成内存泄漏

正确的空间拓展方式

int main(void)
{
	int*ptr=(int*)malloc(100);
	if(ptr!=NULL)
	{
		//业务处理
	}
	else
	{
		return 0;
	}
	
	//现在要拓展空间
	int*p=NULL;
	p=(int*)realloc(ptr,1000);
	if(p!=NULL)
	{
		ptr=p;
	}
	else
	{
		//内存拓展失败的处理
	}
	
	//业务处理
	free(ptr);
	ptr=NULL;
	return 0;
}

c/c++内存开辟的补充

这里补充一下c/c++中程序内存区域划分
在这里插入图片描述

6种常见的动态内存错误

①对NULL指针的解引用操作

void test(void)
{
	int *p=(int*)malloc(4);
	*p=2023; /如果内存开辟失败,p为空指针,解引用就会出错,因此使用前要判断指针是否为空/
	free(p);
}

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

void test(void)
{
	int *p=(int*)malloc(10*sizeof(int));
	if(NULL==p)
	{
		return;
	}
	int i=0;
	for(i=0;i<=10;++i) /i=10时,越界/
	{
		*(p+i)=i;
	}
	free(p);
}

③对非动态开辟的空间使用free释放

void test(void)
{
	int a=2023;
	int*p=&a;
	free(p); /出错/
}

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

void test(void)
{
	int*p=(int*)malloc(16);
	p++;
	free(p); /此时p已经不在指向动态开辟空间的起始位置/
}

⑤对同一块内存多次释放

void test(void)
{
	int*p=(int*)malloc(100);
	free(p);
	free(p); /重复释放,错误/
}

⑥动态内存忘记释放(内存泄露)

void test(void)
{
	int *p=(int*)malloc(100);
	//业务代码
	/没有使用free函数释放动态开辟的空间/
}
int main(void)
{
	test();
	return 0;
}

内存泄露是一种非常严重的错误,很容易造成程序的崩溃
动态内存释放的方式有两种:
· 使用free函数释放
· 程序结束
有些程序是一天24小时都在运行的,如京东这类软件等,如果存在内存泄漏,程序每次调用这个函数时都会泄漏一点内存,而程序又一直运行,等到内存被耗尽时程序就崩溃了。

所以使用动态内存的一个好习惯是:
· 开辟后要判断是否开辟成功
· 使用完后要及时将空间释放
· 空间释放完后将指针置为空

答案与分析

下面来看看前面4道面试题
(一)程序出错
Test函数在调用CreatSpace函数时进行的是值传递(是将str作为参数,而不是传地址过去),CreatSpace函数开辟空间后将地址传给p,但p在CreatSpace函数调用结束后被销毁了,所以str依旧是空指针,strcpy将一个字符串拷贝给空指针,程序出错,同时动态开辟的空间没有释放,造成内存泄漏。

(二)打印出乱码
str虽然获得了CreatSpace中p的地址,但该地址指向的空间在CreatSpace函数调用结束后便还给系统了,此时的str放的是一个野指针,对野指针指向的内容打印便是乱码

(三)正常打印"hello world"
但在使用之前没有判断动态开辟是否成功,同时动态开辟的空间没有释放,造成内存泄漏

(四)打印"world"
但动态开辟的空间没有释放,从而造成内存泄漏

结语

相信通过学习本篇文章,你对动态内存开辟的使用有了一个更好的理解。如果文章中有什么不对的地方,恳请指正,最后不要忘记点赞收藏加关注哦!

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值