CPP遊戲攻略03

前言

今天我們來談一談CPP中很關鍵的兩個操作 new 和 delete ,關於他們與析構函數的關係、動態分配的內存空間、new array ( new[] ) 和 delete array ( delete[] ) 配套出現的原因以及我們可能產生誤解的內存泄漏的形式。

與構造析構函數的羈絆

在一般 CPP 書中,作者都會告訴讀者在調用 new 來動態創建一個對象時,會先分配空間,再調用構造函數。在調用 delete 回收空間時,會先調用析構函數,再釋放內存。這裏我引用第一篇攻略裏的 complex 類,來深入瞭解其內部原理。

假設我們動態創建一個 complex 對象:

Complex *p = new Complex();

編譯器會將其轉化爲三步:

Complex *p;

void *mem = operator new ( sizeof Complex ) ; // 1 分配內存
p = static_cast<Complex*> ( mem ) ;           // 2 類型轉換
p->Complex::Complex(p) ;                      // 3 構造函數

第一步的 operator new 就是一個函數,內部會調用malloc(sizeof Complex)分配一塊內存。
第二步進行類型轉換,從 void 指針轉爲 Complex 指針。
第三步則調用 Complex 構造函數來爲內存中的兩個 double 賦值。其中傳進去的 p 本身就是 this 指針,指向對象自己那一片內存。
cpp0301

我們回收資源:

delete p;

編譯器會轉化爲兩步:

Complex::~Complex(p); // 1 析構函數
operator delete(p);   // 2 釋放內存

第一步調用析構函數,如果類的設計者有什麼事情要在析構函數裏幹(比如釋放成員指針指向的內存),那麼就去幹,沒有則調用默認的析構函數。
第二步的 operator delete 同上也是一個函數,內存會調用free(p);來釋放對應的內存。
cpp0302

內存空間初探

當我們在調用 new 和 delete 時,內存空間究竟是怎麼樣的,認識這一點對於我們理解 new 和 delete 是很有幫助的,但是不同的編譯器提高的方案略有不同,不過大體上的思想一致,本節就以 VC++ 編譯器的做法來展示,g++ 類似。
給出兩個例子:
cpp0303
cpp0304
其中頭尾的 cookie 表示分配的內存塊的大小,注意到圖裏不是寫錯了,而是 cookie 最低的一位表示這個內存塊是被分配的內存塊。除此之外,分配的內存塊大小必須是16的倍數,這裏說的是字節,所以16就是00000011。
在 delete 的時候,就是看的 cookie 的大小來釋放內存,由 cookie 來顯示大小,有 cookie 來釋放內存塊。

delete array

我們說 new array 和 delete array 就是 new[] 和 delete[] 。我們常說 new[] 必須由 delete[] 來收回空間,否則就會造成內存泄漏。那麼是怎麼個泄漏法呢?
首先來看一下 new[] 的內存塊:
cpp0305
cpp0306
與 new 不同的是 new[] 會多出一個 field 來表示計數器,比如上面白色的部分,不過同樣需要16字節對齊。
如果使用 delete[] 來回收內存,那麼需要調用 n 次析構函數,再釋放內存:
cpp0307
但是隻使用 delete ,那麼只會調用 1 次析構函數,但是那一塊內存同樣釋放了。再次提醒,內存釋放需要看 cookie 大小,cookie是多大,就釋放多大的空間。
cpp0308
因此造成內存泄漏,實際上是沒有調用足夠次數的析構函數。所以我們知道,對於沒有指針的類,光用 delete 來釋放內存是完全 OK 的,但是有指針的類,會因爲沒有調用析構函數造成內存泄漏。

總結

我們發現,new[] 的內存塊有時候不需要 delete[] 來配套使用,但是 new delete 一起使用,new[] delete[] 一起使用是基本素養。

Reference

C++面向對象高級編程, 侯捷.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章