拥抱智能指针,告别内存泄露

前言

我们都知道,当申请的内存在不用时忘记释放,导致内存泄漏。长期来看,内存泄漏的危害是巨大的,它导致可用内存越来越少,甚至拖慢系统,最终进程可能被OOM(out of memory)机制杀死。

C与C++中的内存泄漏

在C语言中,我们用malloc申请内存,free释放内存;在C++中,也可以使用它们,不过对于自定义类型,常常会使用new申请,delete来释放。它们都有同样的问题,一旦申请了,但是忘了释放,就会造成内存泄漏,而已经释放了又仍然去访问它,则造成更加直接的严重后果。
一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//main.c
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char *p = NULL;
int i = 0;
while(1)
{
p = malloc(1024);
snprintf(p,1024,"%6d\n",i);
i++;
}
return 0;
}

这是一个很明显的内存泄漏的例子,malloc申请内存后,从来没有释放过,编译运行一段时间后,可能被直接被kill。有兴趣也可以通过top命令观察其内存变化。

也就是说,C/C++中自己用的内存,要自己记得还回去。

而即便有的时候,你记得delete了,但是中间出现异常,导致delete没法执行,同样导致内存泄漏,例如:

1
2
3
Test test = new Test();
/*do some thing*/
delete test;

如果在执行某些操作的时候抛出异常,就可能导致delete无法执行到,从而导致内存泄漏。

Java程序员的幸福

Java程序运行在Java虚拟机上,它有一套垃圾回收(GC)机制,它会定期地回收那些不再被使用的内存,可以有效的防止内存泄露(但不能避免,Java中同样存在内存泄漏)。

但是另外一方面,由于垃圾回收并不是立即的,时机也不是确定的,同时回收机制本身可能比较复杂,会占用空间和时间开销,毕竟C/C++注重效率。

智能指针

为了既能最大程度的避免内存泄漏又能兼顾效率,C++11标准引入了智能指针shared_ptr和unique_ptr。

本文不详细介绍它们的用法,本文旨在通俗地说明它的场景,帮助你理解。

shared_ptr

通常来说,动态申请了一片内存之后,可能会在多个地方会用到,对于裸指针,你需要自己记住在什么地方释放内存,不能在有别的地方还在使用的时候,你就释放,也不能忘记释放。如果是这样,为什么不在有人用的时候,就增加引用计数,而不用的时候(离开作用域或者生命周期外)就较少引用计数呢,如果引用计数为0,则自动释放内存。

举个通俗的例子,假设一个房间里有自动感应灯光。有人在的时候,灯亮了(申请使用内存),再来一个人,这个灯还是亮着,人数增加,而这两个人走掉的时候,房间空了,感应不到人(引用计数为0)的时候,灯就可以自动灭了(自动释放内存),这样也就最大程度地利用了灯光。

不过它的实现要考虑的因素很多,例如如何原子地增加引用计数。所以它在一定程度上比裸指针开销要大。

unique_ptr

与shared_ptr不同,unique_ptr专属某个对象资源。也就是说,如果某个对象有一个专属管理,它不能被复制,那么当这个专属管理不再使用的时候,就可以自动释放内存了。

同样一个通俗的例子,我们现在在很多洗手间都可以看到自动感应的水龙头,一个水龙头通常只供一个人使用(申请并占用资源),而当这个人离开的时候,水龙头自动关闭(自动释放内存)。

而对于老式的水龙头,一旦忘了关了(好像一般也不会忘),就会一直浪费水。

weak_ptr

还有一种情况,对于某些对象,如它可能作为缓存。它有的时候,我就用一下,没有的时候就不用,也不负责去管理资源的释放资源,岂不美哉?

总结

C++新引入的智能指针在使用得当的情况下,可告别内存泄漏。具体用法,我们在后面的文章进行介绍。

守望 wechat
关注公众号[编程珠玑]获取更多原创技术文章
出入相友,守望相助!