2九、【C++基礎】內存泄露

1、內存泄露

一、內存泄露的定義
小程序

  通常咱們常說的內存泄漏是指堆內存的泄漏。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小能夠在程序運行期決定),使用完後必須顯示釋放的內存。數組

  應用程序通常使用malloc,realloc,new等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用free或delete釋放該內存塊,不然,這塊內存就不能被再次使用,咱們就說這塊內存泄漏了。如下這段小程序演示了堆內存發生泄漏的情形:服務器

 1 void MyFunction(int nSize)
 2 {
 3  char* p= new char[nSize];
 4  if( !GetStringFrom( p, nSize ) ){
 5   MessageBox(「Error」);
 6   return;
 7  }
 8 
 9  …//using the string pointed by p;
10  delete p;
11 }

  當函數GetStringFrom()返回零的時候,指針p指向的內存就不會被釋放。這是一種常見的發生內存泄漏的情形。程序在入口處分配內存,在出口處釋放內存,可是c函數能夠在任何地方退出,因此一旦有某個出口處沒有釋放應該釋放的內存,就會發生內存泄漏。函數

  廣義的說,內存泄漏不只僅包含堆內存的泄漏,還包含系統資源的泄漏(resource leak),好比核心態HANDLE,GDI Object,SOCKET, Interface等,從根本上說這些由操做系統分配的對象也消耗內存,若是這些對象發生泄漏最終也會致使內存的泄漏。並且,某些對象消耗的是核心態內存,這些對象嚴重泄漏時會致使整個操做系統不穩定。因此相比之下,系統資源的泄漏比堆內存的泄漏更爲嚴重。性能

GDI Object的泄漏是一種常見的資源泄漏:動畫

 1 void CMyView::OnPaint( CDC* pDC )
 2 {
 3  CBitmap bmp;
 4  CBitmap* pOldBmp;
 5  bmp.LoadBitmap(IDB_MYBMP);
 6  pOldBmp = pDC->SelectObject( &bmp );
 7 
 8 
 9  if( Something() ){
10   return;
11  }
12  pDC->SelectObject( pOldBmp );
13  return;
14 }

  當函數Something()返回非零的時候,程序在退出前沒有把pOldBmp選回pDC中,這會致使pOldBmp指向的HBITMAP對象發生泄漏。這個程序若是長時間的運行,可能會致使整個系統花屏。這種問題在Win9x下比較容易暴露出來,由於Win9x的GDI堆比Win2k或NT的要小不少。spa

二、內存泄漏後果:操作系統

  只發生一次的小的內存泄漏可能不會被注意,但泄漏大量內存的程序或泄漏日益增多的程序可能會表現出各類徵兆:從性能不良(而且逐漸下降)到內存徹底用盡。指針

  更糟的是,泄漏的程序可能會用掉太多內存,以至另外一個程序失敗,而使用戶無從查找問題的真正根源。 此外,即便無害的內存泄漏也多是其餘問題的徵兆。內存泄漏會由於減小可用內存的數量從而下降計算機的性能。code

內存泄漏也會致使較嚴重的後果:

  • 程序運行後置之不理,而且隨着時間的流失消耗愈來愈多的內存(好比服務器上的後臺任務,尤爲是嵌入式系統中的後臺任務,這些任務可能被運行後不少年內都置之不理);
  • 新的內存被頻繁地分配,好比當顯示電腦遊戲或動畫視頻畫面時;
  • 程序可以請求未被釋放的內存(好比共享內存),甚至是在程序終止的時候;
  • 泄漏在操做系統內部發生;
  • 泄漏在系統關鍵驅動中發生;
  • 內存很是有限,好比在嵌入式系統或便攜設備中;
  • 當運行於一個終止時內存並不自動釋放的操做系統(好比AmigaOS)之上,並且一旦丟失只能經過重啓來恢復。

三、內存泄漏的幾種狀況:

(1)在類的構造函數和析構函數中沒有匹配的調用newdelete函數

  兩種狀況下會出現這種內存泄露:

    一是在堆裏建立了對象佔用了內存,可是沒有顯示地釋放對象佔用的內存;

    二是在類的構造函數中動態的分配了內存,可是在析構函數中沒有釋放內存或者沒有正確的釋放內存;

(2)沒有正確的清除嵌套對象的指針

(3)在釋放對象數組時在delete中沒有使用方括號

  方括號是告訴編譯器這個指針指向的是一個對象數組,同時也告訴編譯器正確的對象地址值病調用對象的析構函數,若是沒有方括號,那麼這個指針就被默認爲只指向一個對象,對象數組中的其餘對象的析構函數就不會被調用,結果形成了內存泄露。

  若是在方括號中間放了一個比對象數組大小還大的數字,那麼編譯器就會調用無效對象(內存溢出)的析構函數,會形成堆的奔潰。若是方括號中間的數字值比對象數組的大小小的話,編譯器就不能調用足夠多個析構函數,結果會形成內存泄露。

  釋放單個對象、單個基本數據類型的變量或者是基本數據類型的數組不須要大小參數,釋放定義了析構函數的對象數組才須要大小參數。

(4)指向對象的指針數組不等同於對象數組

  對象數組是指:數組中存放的是對象,只須要delete []p,便可調用對象數組中的每一個對象的析構函數釋放空間;

  指向對象的指針數組是指:數組中存放的是指向對象的指針,不只要釋放每一個對象的空間,還要釋放每一個指針的空間,delete []p只是釋放了每一個指針,可是並無釋放對象的空間,正確的作法,是經過一個循環,將每一個對象釋放了,而後再把指針釋放了;

(5) 缺乏拷貝構造函數

  兩次釋放相同的內存是一種錯誤的作法,同時可能會形成堆的崩潰按值傳遞會調用(拷貝)構造函數,引用傳遞不會調用。

  在C++中,若是沒有定義拷貝構造函數,那麼編譯器就會調用默認的拷貝構造函數,會逐個成員拷貝的方式來複制數據成員,若是是以逐個成員拷貝的方式來複制指針被定義爲將一個變量的地址賦給另外一個變量。

  這種隱式的指針複製結果就是兩個對象擁有指向同一個動態分配的內存空間的指針:

    當釋放第一個對象的時候,它的析構函數就會釋放與該對象有關的動態分配的內存空間。而釋放第二個對象的時候,它的析構函數會釋放相同的內存,這樣是錯誤的。因此,若是一個類裏面有指針成員變量,要麼必須顯示的寫拷貝構造函數和重載賦值運算符,要麼禁用拷貝構造函數和重載賦值運算符

(6) 缺乏重載賦值運算符

  這種問題跟上述問題相似,也是逐個成員拷貝的方式複製對象,若是這個類的大小是可變的,那麼結果就是形成內存泄露;

(7) 關於nonmodifying運算符重載的常見迷思

  a. 返回棧上對象的引用或者指針(也即返回局部對象的引用或者指針)。致使最後返回的是一個空引用或者空指針,所以變成野指針;

  b. 返回內部靜態對象的引用;

  c. 返回一個泄露內存的動態分配的對象。致使內存泄露,而且沒法回收;

  解決這一類問題的辦法是重載運算符函數的返回值不是類型的引用,二應該是類型的返回值,即不是 int&而是int;

(8)沒有將基類的析構函數定義爲虛函數

  當基類指針指向子類對象時,若是基類的析構函數不是virtual,那麼子類的析構函數將不會被調用,子類的資源沒有正確是釋放,所以形成內存泄露;

四、野指針

野指針指向被釋放的或者訪問受限內存的指針。

形成野指針的緣由:

  1. 指針變量沒有被初始化(若是值不定,能夠初始化爲NULL);

  2. 指針被free或者delete後,沒有置爲NULL, freedelete只是把指針所指向的內存給釋放掉,並無把指針自己幹掉,此時指針指向的是「垃圾」內存。釋放後的指針應該被置爲NULL;

  3. 指針操做超越了變量的做用範圍,好比返回指向棧內存的指針就是野指針;

內存泄露總結:

  其實內存泄漏的緣由能夠歸納爲:調用了malloc/new等內存申請的操做,但缺乏了對應的free/delete釋放操做,總之就是,malloc/new比free/delete的數量多。內存用完,再也不使用要及時釋放。

相關文章
相關標籤/搜索