標籤:

C++中為什麼派生類中只有基本類型時,delete一個指向派生類的基類指針時卻沒有內存泄漏?

《Effective C++》條款7

如果將派生類中s1解注釋將內存泄漏

問題是:

為什麼沒有發生內存泄露?

為什麼沒有發生內存泄露?

為什麼沒有發生內存泄露?


C++11 / C++14 §5.3.5/3

In the first alternative (delete object), if the static type of the object to be deleted is different from its
dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the
static type shall have a virtual destructor or the behavior is undefined.

見加粗處。既然你的 QWidget 沒有虛析構函數,那麼行為就是未定義的。編譯器想漏就漏,想不漏就不漏,看心情也好,看月相也罷,反正你管不著。

答案是:

憑什麼要發生內存泄漏?

憑什麼要發生內存泄漏?

憑什麼要發生內存泄漏?

p.s. 題目中提到的 Effective C++ 條款 7 中也有提到。


此問題問的有點 。。。。欠缺思考。

因為,

class C::public P;
P *p = new C; // 內存是用 malloc (sizeof C) 分配出來的。
delete p; // 內存是用 free 釋放的。

請問,如果 C 裡面都是基本類型都是不需要析構的東西,如果有指針也是 null,

那憑什麼要內存泄露呢!哪來的內存給你泄露(當然我說的是你已經 delete p 的情況下)?

而且我還的說,這種情況下,P 和 C 的析構函數是不是 virtual 也都無所謂了。因為我們只需要的是 free (這個在析構函數之後的動作),不管是 C 還是 P 的析構函數都是裡面一行代碼都沒有的函數。

{ 除非 P 和 C 的入口指針不重合(比如說 C 裡面有虛函數而 P 沒有虛函數),難么調錯了析構函數(對 C 的實例調了 P 的析構函數)才會有問題。下面的代碼你們可以自己試試看會是什麼效果。}

#include &
#include &
class P /* 基類 P has not vftable ptr; */
{
public:
P() { strcpy(str1, "1234"); };
~P() { printf("~P: %s
", str1); };
double x1; // 令 P 視角向 8 bytes 對齊。
char str1[8];
};
class C : public P /* 子類 C has vftable ptr; */
{
public:
C() { strcpy(str2, "5678"); };
~C() { printf("~C: %s
", str2); };
virtual int foo(void) { return 100; }; // 另 C 的實例有 vftable ptr
char str2[8];
};
int main(int argc, char* argv[])
{
P *p = new C(); // 指針轉換時地址存在偏差(編譯器負責調整)

// try: uncomment this line to free p correctly;
// p = (P*)((char*)p - sizeof(double));

// free 時和 malloc 返回地址錯開 8 bytes,可能導致運行時錯誤
delete p; /* maybe occur runtime error ! */
return 0;
}

基本數據類型,都存在於在這塊內存之內。無析構之負擔。

但是如果裡面另有一個對象 x,如果該 x 裡面也都是基本類型,同樣也無內存泄露(這是因為 x 由基本類型組成,它也沒有析構之負擔),

但是:

如果 x 裡面又分配了內存,則此對象相當於含有二級指針,則必須調用 x 的析構函數。

如果你不調用 x 的析構函數,當然內存泄露了!


你把std::string s;放到widget裡面並改成std::string s,然後在構造函數裡面給他一個嚴格超過若干位元組的字元串(VC++我記得是要超過16位元組,你可以給更長),你就能看到內存泄漏了。


我理解new的時候要傳入內存塊的大小,而此時子類基本類型成員的大小已經包括在內,delete的時候會一起被釋放掉,只有當子類的操作中再次動態申請了內存,這片內存將不能通過基類指針delete掉,因此基類需要申明虛析構函數,在子類析構函數中釋放這些內存


new運算符分兩步:(1) 分配sizeof(class) 大小的內存,返回指針pst, (2) 對象構造函數初始化

對應的delete也分為兩步: (1) 調用對象的析構函數, (2) 釋放new時分配的pst指向的內存塊

對於派生類的成員變數都是基本數據類型的情況下,子類對象new時分配sizeof(widget)大小的內存,返回指針pst,並且在pst指向的前會記錄此次分配的內存的大小 (參考malloc的隱式鏈表實現); delete基類的時候釋放pst指向的內存塊時,依據記錄的size進行資源回收, 因此這種情況下,即使沒有調用子類析構函數,也不會造成內存泄露


顯然, 從你的代碼來看, 你的基類並沒有 虛析構函數, 那麼如果:

QWidget * a = (QWidget * )new Widget;

然後 delete a;

那麼究竟會不會發生內存泄漏?

是哪一部分的內存泄漏?

對於此問題, 一共有兩種觀點:

其一:

對象a 的內存 = Widget的內存

而Widget的內存 則包含了QWidget 的內存

那麼 如果 delete a,

由於a 是 QWidget 類型, 那麼就會釋放掉a內存段中 QWidget 部分的內存...

剩下的內存就不釋放,造成內存泄漏

其二:

現在流行一種觀點, 認為 new 有記錄功能.意思就是說

QWidget * a = (QWidget * )new Widget;

new 申請的內存的大小是 Widget 的內存大小, 然後就自動記錄下來了

一旦你調用 delete a ; 雖然a 是 QWidget 的類型, 但是由於new記錄了 a 對應內存是Widget那麼大的. 所以釋放內存時, 把整片 Widget; 內存全部釋放, 不會內存泄漏.

另外:

無論 第二個觀點是否正確, 這樣子釋放對象內存的做法都是不入流的.

我見過一些工程源碼;一般的專業程序員都必須這麼做:

在 任何 類裡面定義一個 虛析構 函數

比如在 QWidget 類裡面定義一個 : virtual ~QWidget () {}

這樣子做了之後, 你 delete a; (a是QWidget 類型)

delete時, 先釋放 QWidget 部分的內存, 釋放完成之後, 由於 virtual 具有繼承的特性, 就會輪到 Widget 調用析構.

這麼一來, 基類和子類就被先後析構釋放內存了.

就不會發生內存泄漏.. 推薦你使用該做法.


基類析構函數要用虛函數。這樣析構時才會先刪派生類再刪基類,否則只會delete基類,派生類的析構函數不會調用,從而產生內存泄露。


因為派生類只有基本的類型的話, delete x雖然沒有調用派生類的析構函數(因為基類的析構不是虛函數), 也是能正確釋放內存的, 因為一般x所指向的內存前面會有記錄實際分配了多少內存


所以才要求基類有虛析構函數,否則行為是未定義的


因為 string 裡面沒用 new 分配空間,較短的字元串直接內部開個小數組存了,除非你給它賦值一個比較長的字元串


書上的原話:當derived class對象經由一個base class指針被刪除,而該base class帶著一個non-virtual析構函數,其結果未有定義----實際執行時通常發生的是對象的derived成分沒被銷毀。

問題出在最後一句:what typically happens at runtime is that the derived part of the object is never destroyed.(特意查了查英文版,發現翻譯沒問題)。

首先這句話是對的。但作者這樣說卻給我們了一種誤導,使我們誤以為派生類沒被銷毀的原因是派生類的destructor沒被調用。但事實上,一個對象被delete前未必要成功調用該對象destructor(詳見《深度探索c++對象模型》P233)

分析題主的問題:為什麼沒有泄露?

答:因為delete將new出來的那片內存完全歸還給了操作系統。當然,題主的直覺是正確的:delete時派生類的destructor沒被調用。

想一想題主的派生類的destructor里有什麼?其實什麼也沒有,該destructor是trivial的,所以也沒必要被調用。但是如果派生類的成員里有動態類型的成員例如string,那麼該派生類會自動生成一個destructor調用其成員string的destuctor,而這時,如果派生類的destructor在派生類銷毀時不能被調用(基類有non-virtual destructor),那麼string成員分配的內存得不到釋放,才會引起內存泄露。

手機打字沒有(′???`)沒有栗子。

不嚴謹有錯誤的地方望指出


因為delete一個指針,回收的內存多少跟指針指什麼類型無關,new的時候都已經記好了。

你這樣delete一個指向基類的指針的話,派生類裡面如果有類型使用了動態分配的內存的話,它是不知道要釋放的,就算是智能指針指著也不行。


推薦閱讀:

c++在執行運行時多態時,為什麼需要借用rtti來判斷對象真實類型?
一個關於C++模板的問題?
關於c++模板推導失敗,這是編譯器的bug嗎?
《深度探索c++對象模型》,C語言有沒有類似的書,講解C語言低層細節及編譯器所做工作?
計算機語言是有局限性的么?

TAG:CC |