C语言之动态内存管理所遇到的常见错误

相信经过我们上一篇博客对动态内存管理的了解,我们对这部分知识已经大体了解了。我们在完成关于动态内存存储的有关工程时,经常会出现代码错误的情况,一般包括以下几种,我们一起来分析一下


目录

1.对NULL指针的解引用操作

1.1错误代码

1.2正确代码

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

2.1错误代码

2.2正确代码

3.对非动态开辟的内存空间进行free释放

3.1错误代码

3.2正确代码

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

4.1错误代码

4.2正确代码

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

5.1错误代码

5.2正确代码

6.动态开辟内存空间忘记释放

6.1错误代码

6.2正确代码

7.动态开辟内存在释放前程序结束

7.1错误代码

7.2正确代码


在分析之前,我们首先要知道free函数和动态内存开辟函数必须成对使用

1.对NULL指针的解引用操作

1.1错误代码

这是什么意思呢,就是未对我们开辟出来的动态空间进行判断是否开辟成功,就对指针进行了应用

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

//对NULL指针解引用操作问题

int main()
{
	int* p = (int*)malloc(sizeof(int)*400000000000);

	int i = 0;
	for (i = 0; i <10; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
    return 0;
}

我们在有关malloc函数的博客中说过,malloc函数开辟内存也不是百分百会成功,需要开辟的空间足够大它就会失败,如果malloc开辟内存失败,会返回一个空指针,但是这里没有对它进行判断就直接使用,p如果为空指针,函数的后续使用就有问题,代码不安全

我们运行发现,代码没有结果,我们存入内存的数也没有打印,此时malloc开辟内存失败

1.2正确代码

其实有两种解决办法

1.方法一:加上判断语句,判断malloc是否开辟内存成功成功则继续执行,不成功,则结束代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

//对NULL指针解引用操作问题

int main()
{
	int* p = (int*)malloc(sizeof(int)*400000000000);
	//判断malloc函数是否开辟内存成功
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

	int i = 0;
	for (i = 0; i <10; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

及时截断代码,告诉我们错误信息,malloc开辟皮内存不成功,没有足够的空间

2.方法二:使用断言assert函数,判断指针p是否为空指针,但assert只会告诉你它是空指针,不会打印原因

int main()
{
	int* p = (int*)malloc(sizeof(int) * 400000000000);
	assert(p);

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

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

意思就是说,你假如只开辟了10个整型的空间,但是你后续使用这块空间想使用20个,那么多使用的这10个就属于越界访问

2.1错误代码

int main()
{
	int* p = (int*)malloc(100);//这里开辟了25个整型空间
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 0; i < 200; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

我们只开辟了25个int型的空间,但是我们向后使用了200个整型空间,这就属于越界访问,这是违法的,虽然可以打印出我们要求的数据,但这是不安全的,在有些编译器里面会显示代码错误。

我们在VS编译器中,虽然有打印出结果,但是程序却一直不结束,这也足以代码存在问题

2.2正确代码

解决这个问题,只需要我们开辟出多少空间就使用多少空间,不多用

int main()
{
	int* p = (int*)malloc(100);//这里开辟了25个整型空间
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 0; i < 25; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

我们运行代码会发现,代码很流畅,不存在刚刚的卡顿

3.对非动态开辟的内存空间进行free释放

我们要知道,free释放空间只适用于动态开辟的内存空间,也就是堆区的,不能释放存放在栈区的空间

3.1错误代码

//对非动态开辟的内存使用free释放

int main()
{
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
	return 0;
}

运行发现我们电脑有点死机,这就是因为我们用free释放了栈区的空间,存放在栈区的代码不需要程序员来释放,函数执行结束时,这些存储单元自动被释放。

3.2正确代码

它的真确书写很简单,只要不要用free释空间即可,即去掉free



int main()
{
	int a = 10;
	int* p = &a;

	return 0;
}

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

顾名思义,就是释放了一部分空间,还剩下一部分在内存中没有被释放

4.1错误代码


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

int main()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 25; i++)
	{
		*p = i + 1;
		//这里p的起始地址发生了变化
		p++;
	}
	free(p);
	p = NULL;
	return 0;
}

我们可以看到free(p)释放的只是开辟好的内存空间的最后一个地址指向的空间,因为p的地址发生了改变,所以没有完全释放这100个字节的空间,运行发现我们的电脑在这个程序里也有点死机,可见这是一种非常不友好的行为

4.2正确代码

解决这个问题,我们要让指针p的地址不发生改变,一直指向开辟好空间的起始地址,这样就能完全释放



int main()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 25; i++)
	{
		*(p+i) = i + 1;
	}
	free(p);
	p = NULL;
	return 0;
}

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

就是我们对这一块开辟好的动态内存使用完后进行了多次free释放,这是针对指针释放后未被置空(NULL)的时候

5.1错误代码

//对同一块动态开辟空间多次释放

int main()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用p
	int i = 0;
	for (i = 0; i < 25; i++)
	{
		*(p+i) = i + 1;
	}
	//释放
	free(p);
	//使用p
	int j = 5;
	while (j--)
	{
		*(p + j) = j;
	}
	//释放p
	free(p);
	return 0;
}

我们多次使用free函数释放指针p,同一块动态内存空间不能被释放两次,此时代码就会产生错误

5.2正确代码

解决这个问题我们只要把p指针释放后在置为空指针(NULL),那么后面我们在释放几次p,代码也不会发生错误。因为free规定,若要释放的指针是空指针,free函数什么事也不做,自然不会报错。

int main()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用p
	int i = 0;
	for (i = 0; i < 25; i++)
	{
		*(p+i) = i + 1;
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
	//当然我们前面释放了p指向的这块空间,后面我们就不可以再使用p,p此时已经是空指针了
	//int j = 5;
	//while (j--)
	//{
	//	*(p + j) = j;
	//}

	//释放p
	free(p);
	return 0;
}

6.动态开辟内存空间忘记释放

就是说,你申请了一块动态空间忘记释放,这种情况存在内存泄漏

6.1错误代码

//动态开辟内存空间忘记释放

void Test(void)
{
	//开辟空间
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return;
	}
	//使用这块空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}
}
int main()
{
	Test();
	return 0;
}

我们可以看到,在程序中,在函数使用完这块空间后,没有释放这块空间。那么当代码执行完成Test函数时,我们已经来不及释放这块动态空间了,因为p指向的这块动态空间是在Test函数中申请的,p属于局部变量,出了Test函数,p就已经被销毁了,此时就无法知道这100个字节的空间被开辟在什么位置了。

只有当这个程序完全结束时,这100个字节的空间才会被释放

依旧会打印,但存在内存泄漏,不安全

6.2正确代码

解决这个问题,就是需要我们在使用完这块空间以后及时释放掉,确保不存在内存泄漏

当我们自己使用这块空间时:

//自己用
void Test(void)
{
	//开辟空间
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return;
	}
	//使用这块空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
}
int main()
{
	Test();
	return 0;
}

当还有别人要使用这块空间时,此时我们必须注释清楚,给别人一定的提示:


//还有别人使用

//函数内部进行了malloc操作,返回了malloc开辟空间的起始地址,记得释放
int* Test(void)
{
	//开辟空间
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//使用这块空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}
	return p;
	因为还有别人使用,我们在这里不进行释放
	//free(p);
	//p = NULL;
}
int main()
{
	int* ptr=Test();
	//别人开始使用
	int i = 0;
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		*(ptr + i) = i + 1;
		printf("%d ", *(ptr + i));
	}
	//使用完成,进行释放
	free(ptr);
	ptr = NULL;
	return 0;
}

运行代码,我们可以看出,这样的操作完全没有问题

7.动态开辟内存在释放前程序结束

这种情况就是说,虽然动态开辟内存函数与free成对使用了,但是在执行free函数之前,代码就已经结束了,此时存在内存泄漏

7.1错误代码

//动态开辟内存在释放前程序结束

void Test(void)
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return;
	}
	//使用这块空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}
	//在这里,如果满足条件,开辟好的空间在未被使用之前就结束了,没有释放
	if (1)
	{
		return;
	}
	//	释放
	free(p);
	p = NULL;
}
int main()
{
	Test();
	return 0;
}

此时,我们开辟好的动态空间没有被释放,存在内存泄漏,我们写代码的时候要尽量避免这种情况

它不安全

7.2正确代码

解决这个问题,我们要查看在使用完这块动态空间,同时在后续不在用到这块空间后,这块空间有没有被立马释放

void Test(void)
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		perror("malloc");
		return;
	}
	//使用这块空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
		printf("%d ", *(p + i));
	}	
	//	释放
	free(p);
	p = NULL;
	在这里,如果满足条件,开辟好的空间在未被使用之前就结束了,没有释放
	//if (1)
	//{
	//	return;
	//}

}
int main()
{
	Test();
	return 0;
}

这样就是安全的,不存在内存泄漏


好了,今天的内容就到这里,这也就是我们动态管理空间的绝大部分内容了,大家要好好掌握,我们下期再见!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月亮夹馍干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值