擁抱智慧指針,告別記憶體泄露

  • 2019 年 11 月 13 日
  • 筆記

前言

我們都知道,當申請的記憶體在不用時忘記釋放,導致記憶體泄漏。長期來看,記憶體泄漏的危害是巨大的,它導致可用記憶體越來越少,甚至拖慢系統,最終進程可能被OOM(out of memory)機制殺死。

C與C++中的記憶體泄漏

在C語言中,我們用malloc申請記憶體,free釋放記憶體;在C++中,也可以使用它們,不過對於自定義類型,常常會使用new申請,delete來釋放。它們都有同樣的問題,一旦申請了,但是忘了釋放,就會造成記憶體泄漏,而已經釋放了又仍然去訪問它,則造成更加直接的嚴重後果。 一個簡單的例子:

//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,"%6dn",i);          i++;      }      return 0;  }

這是一個很明顯的記憶體泄漏的例子,malloc申請記憶體後,從來沒有釋放過,編譯運行一段時間後,可能被直接被kill。有興趣也可以通過top命令觀察其記憶體變化。

也就是說,C/C++中自己用的記憶體,要自己記得還回去。

而即便有的時候,你記得delete了,但是中間出現異常,導致delete沒法執行,同樣導致記憶體泄漏,例如:

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++新引入的智慧指針在使用得當的情況下,可告別記憶體泄漏。具體用法,我們在後面的文章進行介紹。