前言
在动态内存函数的使用过程中我们可能会遇到一些错误,这里将常见的错误进行总结。
对NULL解引用
请看以下代码:
可以看到,这时我们的malloc开辟是失败的,所以返回的是空指针NULL,而我们却没有进行检查,而是直接对其解引用,最终想要打印这块内存的内容,也是失败的。
这里的下波浪线是vs给出的没有进行检查的警告,即使malloc申请的空间没有那么大可以成功申请,最终也能正常打印,这个警告依然会存在。
总之,就是不要忘记检查返回值是否为NULL。
对动态开辟空间的越界访问
动态开辟的空间也有自己的大小,所以也有越界。
可以看到,此时vs很智能地给出了警告。
对非动态开辟内存使用free释放
int main()
{
int a = 2077;
int* p = &a;
free(p);
p = NULL;
return 0;
}
这段代码中,p指向的明明不是动态开辟来的空间,却对它进行free,会发生什么呢?
可以看到会出现这样的后果。(动态内存相关的错误常见现象)
使用free释放动态开辟内存的一部分
我们知道,如果用*(p+i),在访问数组的过程中,i在改变,但p始终没有改变。
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for(i=0;i<5;i++)
{
*(p+i)=i;
}
free(p);
p=NULL;
而我们还有另一种移动指针的写法:
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for(i=0;i<5;i++)
{
*p=i;
p++;
}
free(p);
p=NULL;
在这种写法中,p自身发生了改变。当退出循环时,由于p++,此时我们的p指向的是第6个元素(数组只有5个元素)。所以我们的p现在不指向动态开辟内存的起始位置。而使用free必须是传起始地址。
所以,我们不应该让p乱动。
对同一块动态内存多次释放
如果是这样的多次释放:
free(p);
p = NULL;
free(p);
p = NULL;
这倒是没什么问题,因为前面已经把p置为NULL了,而对NULL进行释放会什么都不做。
但如果是这样的多次释放,就会有问题:
free(p);
free(p);
p = NULL;
因为我们并没有把p置为NULL,那么此时p成为了野指针,我们对野指针进行释放,这是有问题的。
所以又可以再次看出,将p释放后及时置为NULL的重要性。
动态开辟内存忘记释放(内存泄漏)
这是各类问题中相当令人头疼的一个。开辟了内存,也使用了,但是忘记释放了。
比如,我们在调用free()之前写了会提前返回的代码(这是很有可能发生的):
void test()
{
int flag = 1;
int* p = (int*)malloc(100);
if (p == NULL)
{
return;
}
//假设这里使用了这块内存
if (flag)//假设某个条件发生了,就提前返回
return;
free(p);
p=NULL;
}
int main()
{
test();
//假设这里还有很多代码
return 0;
}
所以这时,我们开辟来的这块空间没有机会释放了。
而且从test回到主函数后,还有很多代码,已经找不回这块空间了,也就是内存泄漏了。
只有等到主函数彻底结束,这块空间才会回收。
内存泄漏指的是一块空间动态开辟后,使用完又不释放,也可能再没法释放,这块空间就相当于消失或者说泄漏了。
所以可以看到,即使我们的malloc和free已经成对使用了,也有可能出现无法释放的情况,这样的内存泄漏的问题必须在写代码时小心,在出问题时也得慢慢去查。
(如果想让后面的程序去释放,那就要把指针返回给后面的程序)
内存泄漏有多可怕呢,比如一些服务器程序,24/7,一直在运行,如果内存泄漏,一会吃掉一点内存,可能就把内存耗干了,机器就挂了。
总结
动态内存是一把双刃剑,能够提供灵活的内存管理方式,同时也会带来风险。
到此,本文就结束了,祝阅读愉快^_^