new 和 delete 背後機制

2021-07-30 23:40:20 字數 3541 閱讀 5347

定義乙個類 a:

class a

~a()

private:

intvar;

file *file;

};

很簡單,類 a 中有兩個私有成員,有乙個建構函式和乙個析構函式,建構函式中初始化私有變數 var 以及開啟乙個檔案,析構函式關閉開啟的檔案。

我們使用

class *pa = new

a(10);

來建立乙個類的物件,返回其指標 pa。如下圖所示 new 背後完成的工作:

簡單總結一下:

首先需要呼叫上面提到的 operator new 標準庫函式,傳入的引數為 class a 的大小,這裡為 8 個位元組。這樣函式返回的是分配記憶體的起始位址,這裡假設是 0x007da290。

上面分配的記憶體是未初始化的,也是未型別化的,第二步就在這一塊原始的記憶體上對類物件進行初始化,呼叫的是相應的建構函式,這裡是呼叫 a:a(10); 這個函式,從圖中也可以看到對這塊申請的記憶體進行了初始化,var=10, file 指向開啟的檔案。

最後一步就是返回新分配並構造好的物件的指標,這裡 pa 就指向 0x007da290 這塊記憶體,pa 的型別為類 a 物件的指標。

所有這三步,你都可以通過反彙編找到相應的彙編**,在這裡我就不列出了。

好了,那麼 delete 都幹了什麼呢?還是接著上面的例子,如果這時想釋放掉申請的類的物件怎麼辦?當然我們可以使用下面的語句來完成:

delete pa;
delete 所做的事情如下圖所示:

delete 就做了兩件事情:

呼叫 pa 指向物件的析構函式,對開啟的檔案進行關閉。

通過上面提到的標準庫函式 operator delete 來釋放該物件的記憶體,傳入函式的引數為 pa 的值,也就是 0x007d290。

好了,解釋完了 new 和 delete 背後所做的事情了,是不是覺得也很簡單?不就多了乙個建構函式和析構函式的呼叫嘛

我們經常要用到動態分配乙個陣列,也許是這樣的:

string *psa = new string[10]; //array of 10 empty strings

int *pia = new int[10]; //array of 10 uninitialized ints

上面在申請乙個陣列時都用到了 new 這個表示式來完成,按照我們上面講到的 new 和 delete 知識,第乙個陣列是 string 型別,分配了儲存物件的記憶體空間之後,將呼叫 string 型別的預設建構函式依次初始化陣列中每個元素;第二個是申請具有內建型別的陣列,分配了儲存 10 個 int 物件的記憶體空間,但並沒有初始化。

如果我們想釋放空間了,可以用下面兩條語句:

delete psa;

delete pia;

都用到 delete 表示式,注意這地方的 一般情況下不能漏掉!我們也可以想象這兩個語句分別幹了什麼:第乙個對 10 個 string 物件分別呼叫析構函式,然後再釋放掉為物件分配的所有記憶體空間;第二個因為是內建型別不存在析構函式,直接釋放為 10 個 int 型分配的所有記憶體空間。

這裡對於第一種情況就有乙個問題了:我們如何知道 psa 指向物件的陣列的大小?怎麼知道呼叫幾次析構函式?

這個問題直接導致我們需要在 new 乙個物件陣列時,需要儲存陣列的維度,c++ 的做法是在分配陣列空間時多分配了 4 個位元組的大小,專門儲存陣列的大小,在 delete 時就可以取出這個儲存的數,就知道了需要呼叫析構函式多少次了。

還是用圖來說明比較清楚,我們定義了乙個類 a,但不具體描述類的內容,這個類中有顯示的建構函式、析構函式等。那麼 當我們呼叫

class a *paa = new a[3];
時需要做的事情如下:

從這個圖中我們可以看到申請時在陣列物件的上面還多分配了 4 個位元組用來儲存陣列的大小,但是最終返回的是物件陣列的指標,而不是所有分配空間的起始位址。

這樣的話,釋放就很簡單了:

這裡要注意的兩點是:

呼叫析構函式的次數是從陣列物件指標前面的 4 個位元組中取出;

傳入 operator delete 函式的引數不是陣列物件的指標 paa,而是 paa 的值減 4。

從上面解釋的你應該懂了 new/delete、new/delete 的工作原理了,因為它們之間有差別,所以需要配對使用。但偏偏問題不是這麼簡單,這也是我遇到的問題,如下這段**:

int *pia = new

int[10];

delete pia;

這肯定是沒問題的,但如果把 delete pia; 換成 delete pia; 的話,會出問題嗎?

這就涉及到上面一節沒提到的問題了。上面我提到了在 new 時多分配 4 個位元組的緣由,因為析構時需要知道陣列的大小,但如果不呼叫析構函式呢(如內建型別,這裡的 int 陣列)?我們在 new 時就沒必要多分配那 4 個位元組, delete 時直接到第二步釋放為 int 陣列分配的空間。如果這裡使用 delete pia;那麼將會呼叫 operator delete 函式,傳入的引數是分配給陣列的起始位址,所做的事情就是釋放掉這塊記憶體空間。不存在問題的。

這裡說的使用 new 用 delete 來釋放物件的提前是:物件的型別是內建型別或者是無自定義的析構函式的類型別!

我們看看如果是帶有自定義析構函式的類型別,用 new 來建立類物件陣列,而用 delete 來釋放會發生什麼?用上面的例子來說明:

class a *paa = new

class a[3];

delete paa;

那麼 delete paa; 做了兩件事:

顯然,這裡只對陣列的第乙個類物件呼叫了析構函式,後面的兩個物件均沒呼叫析構函式,如果類物件中申請了大量的記憶體需要在析構函式中釋放,而你卻在銷毀陣列物件時少呼叫了析構函式,這會造成記憶體洩漏。

上面的問題你如果說沒關係的話,那麼第二點就是致命的了!直接釋放 paa 指向的記憶體空間,這個總是會造成嚴重的段錯誤,程式必然會奔潰!因為分配的空間的起始位址是 paa 指向的地方減去 4 個位元組的地方。你應該傳入引數設為那個位址!

同理,你可以分析如果使用 new 來分配,用 delete 來釋放會出現什麼問題?是不是總會導致程式錯誤?

總的來說,記住一點即可:new/delete、new/delete 要配套使用!

深入new和delete小結

new和delete是c 中使用頻率非常高的兩個關鍵字,可以說c 記憶體操作的核心就在於這兩個關鍵字,近幾天閱讀了相關的文章,發現自己對於這兩個關鍵字的理解太過膚淺,因此做了一些個人總結。首先要明確一點,new和delete所操作的記憶體全部是在堆區,這個區域的記憶體和棧區是不一樣的,不會自動釋放,...

New和delete的原理

new和delete的原理 當我們在程式中寫下 new 和 delete 時,我們實際上呼叫的是 c 語言內建的 new operator 和 delete operator.所謂語言內建就是說我們不能更改其含義,它的功能總是一致的。以 new operator 為例,它總是先分配足夠的記憶體,而後...

多型與new和delete

在使用多型時,在new和delete時一定要保持原有物件型別,不能在new時就進行強制轉換,否則會在析構時出現問題。其他時候可以使用 如下面的例子 class cbase cbase virtual void call class cinherit public cbase cinherit vir...