【c++學習筆記】深度解析new/delete以及new[]/delete[]
在 C++ 中,你也許經常使用 new 和 delete 來動態申請和釋放記憶體,但你可曾想過以下問題呢?
- new 和 delete 是函式嗎?
- new [] 和 delete [] 又是什麼?什麼時候用它們?
- 你知道 operator new 和 operator delete 嗎?
- 為什麼 new [] 出來的陣列有時可以用 delete 釋放有時又不行?
- …
new 和 delete 到底是什麼?
- 如果找工作的同學看一些面試的書,我相信都會遇到這樣的題:sizeof 不是函式,然後舉出一堆的理由來證明 sizeof 不是函式。在這裡,和 sizeof 類似,new 和 delete 也不是函式,它們都是 C++ 定義的關鍵字,通過特定的語法可以組成表示式。和 sizeof 不同的是,sizeof 在編譯時候就可以確定其返回值,new 和 delete 背後的機制則比較複雜。
繼續往下之前,請你想想你認為 new 應該要做些什麼?也許你第一反應是,new 不就和 C 語言中的 malloc 函式一樣嘛,就用來動態申請空間的。你答對了一半,看看下面語句:
string *ps = new string("hello world");
- 你就可以看出 new 和 malloc 還是有點不同的,malloc 申請完空間之後不會對記憶體進行必要的初始化,而 new 可以。所以 new expression 背後要做的事情不是你想象的那麼簡單。在我用例項來解釋 new 背後的機制之前,你需要知道
operator new
和operator delete
是什麼玩意。
operator new 和 operator delete
- 這兩個其實是 C++ 語言標準庫的庫函式,原型分別如下:
void *operator new (size_t); //allocate an object
void *operator delete(void *); //free an object
void *operator new[](size_t); //allocate an array
void *operator delete[](void *); //free an array
後面兩個你可以先不看,後面再介紹。前面兩個均是 C++ 標準庫函式,你可能會覺得這是函式嗎?請不要懷疑,這就是函式!C++ Primer 一書上說這不是過載 new 和 delete 表示式(如 operator= 就是過載 = 操作符),因為 new
operator new
和operator delete
來命名,比較費解。我們只要知道它們的意思就可以了,這兩個函式和 C 語言中的 malloc 和 free 函式有點像了,都是用來申請和釋放記憶體的,並且 operator new 申請記憶體之後不對記憶體進行初始化,直接返回申請記憶體的指標。我們可以直接在我們的程式中使用這幾個函式。
new 和 delete 背後機制
- 知道上面兩個函式之後,我們用一個例項來解釋 new 和 delete 背後的機制
- 我們不用簡單的 C++ 內建型別來舉例,使用複雜一點的類型別,定義一個類 A:
class A
{
public:
A(int v) : var(v)
{
fopen_s(&file, "test", "r");
}
~A()
{
fclose(file);
}
private:
int var;
FILE *file;
};
- 很簡單,類 A 中有兩個私有成員,有一個建構函式和一個解構函式,建構函式中初始化私有變數 var 以及開啟一個檔案,解構函式關閉開啟的檔案。
我們使用
class A *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 就做了兩件事情:
- 呼叫 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 個位元組用來儲存陣列的大小,但是最終返回的是物件陣列的指標,而不是所有分配空間的起始地址。
這樣的話,釋放就很簡單了:
delete []pAa;
注意:
- 呼叫解構函式的次數是從陣列物件指標前面的 4 個位元組中取出;
- 傳入 operator delete[] 函式的引數不是陣列物件的指標 pAa,而是 pAa 的值減 4。
為什麼 new/delete 、new []/delete[] 要配對使用?
- 從上面解釋的你應該懂了 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 指向的物件的解構函式;
- 呼叫 operator delete(pAa); 釋放記憶體。
顯然,這裡只對陣列的第一個類物件呼叫了解構函式,後面的兩個物件均沒呼叫解構函式,如果類物件中申請了大量的記憶體需要在解構函式中釋放,而你卻在銷燬陣列物件時少呼叫了解構函式,這會造成記憶體洩漏。
面的問題你如果說沒關係的話,那麼第二點就是致命的了!直接釋放 pAa 指向的記憶體空間,這個總是會造成嚴重的段錯誤,程式必然會奔潰!因為分配的空間的起始地址是 pAa 指向的地方減去 4 個位元組的地方。你應該傳入引數設為那個地址!
同理,你可以分析如果使用 new 來分配,用 delete [] 來釋放會出現什麼問題?是不是總會導致程式錯誤?
總的來說,記住一點即可:new/delete、new[]/delete[] 要配套使用總是沒錯的!
malloc/free與new/delete的區別與聯絡
1.區別:
- malloc/free是C/C++標準庫的函式,new/delete是C++操作符
- malloc/free只是動態分配記憶體空間/釋放空間。而new/delete除了
- 分配空間還會呼叫建構函式和解構函式進行初始化與清理(清理成員)
- malloc/free需要手動計算型別大小且返回值會void*,new/delete可自己計算型別的大小,返回對應型別的指標
2.聯絡:
- 它們都是動態管理記憶體的入口
- new/delete會呼叫operator new/operator delete,實際operator new和operator delete只是malloc和free的一層封裝
相關推薦
【c++學習筆記】深度解析new/delete以及new[]/delete[]
在 C++ 中,你也許經常使用 new 和 delete 來動態申請和釋放記憶體,但你可曾想過以下問題呢? new 和 delete 是函式嗎? new [] 和 delete [] 又是什麼?什麼時候用它們? 你知道 operator new 和 oper
【c++學習筆記】深入解析淺拷貝與深拷貝
測試環境:vs2013 什麼是淺拷貝 也稱位拷貝,編譯器只是將物件中的值拷貝過來,如果物件中管理資源,最後就會導致多個物件共享同一份資源,當一個物件銷燬時就會將該資源釋放掉,而此時另一些物件不知道該資源已經被釋放,以為還有效,所以當繼續對資源進行操作時
【C++ 學習筆記】 MFC CEdit
top 註意 hello lac int 生成 屬性對話框 關聯 額外 環境:VS2008 -關聯變量 方法一:在控件上右鍵-變量-CEdit類型變量-輸入變量名。 方法二:添加代碼 頭文件中定義變量CEdit m_edit_fileName;源文件中變量
【C#學習筆記】 IDisposable 接口
數據 在外 source int word 編寫 osi 圖標 不知道 托管資源指的是.NET可以自動進行回收的資源,主要是指托管堆上分配的內存資源。托管資源的回收工作是不需要人工幹預的,有.NET運行庫在合適調用垃圾回收器進行回收。 非托管資源指的是.
【C#學習筆記】播放wav文件
con sound ram pan layer oid col med clas using System; using System.Media; namespace ConsoleApplication { class Program
【C#學習筆記】Dictionary容器使用
main 容器 ons move eap app namespace ica gen using System; using System.Collections.Generic; namespace ConsoleApplication { cla
【C#學習筆記】寫文件
pan name iter create open static col main write using System; using System.IO; namespace ConsoleApplication { class Program
【C#學習筆記】網頁彈出提示框
app c# tar code script dfs 提示框 form pro using System; using System.Collections.Generic; using System.Linq; using System.Web; using
【C#學習筆記】獲得本機IP
tostring class main style dns system pad space ipad using System; using System.Net; namespace ConsoleApplication { class Prog
【C#學習筆記】函數調用
() void color space c# ram 學習 pro urn using System; namespace ConsoleApplication { class Program { static int Ad
【C#學習筆記】瀏覽目錄得到路徑
lin log ini art sys 目錄 dialog forms cati using System; using System.Collections.Generic; using System.ComponentModel; using System.D
【C#學習筆記】獲取當前應用程序所在路徑及環境變量
環境 nbsp for filename 字符 dom process ati 反斜杠 轉自:http://www.cnblogs.com/netlyf/archive/2011/06/22/2086718.html 一、獲取當前文件的路徑 string str1=Pr
【C#學習筆記】載入圖片並居中
mode collect angle mage event names args void fromfile using System; using System.Collections.Generic; using System.ComponentModel;
【C#學習筆記】保存文件
ica cnblogs void click lec system esp open () using System; using System.Collections.Generic; using System.ComponentModel; using Sys
【C#學習筆記】播放wma/mp3文件
con cati ise returns c# 停止 end ext resume using System; using System.Runtime.InteropServices; namespace ConsoleApplication {
【C#學習筆記】讀SQL Server2008
data ext lose tar inf lec area space args using System; using System.Data.SqlClient; namespace ConsoleApplication { class Pro
【C#學習筆記】讀access2007
ole cat sys source class void sel mes nec using System; using System.Data.OleDb; namespace ConsoleApplication { class Program
【C#學習筆記】類構造函數使用
print program ica name eap code ogr cnblogs xiaomi using System; namespace ConsoleApplication { class stu { priv
【C#學習筆記】using 三種使用方式
ride over 啟動 類型 thread catch key log 調用 1.using指令。using + 命名空間名字,這樣可以在程序中直接用命令空間中的類型,而不必指定類型的詳細命名空間,類似於Java的import,這個功能也是最常用的,幾乎每個cs的程序都會
【c# 學習筆記】類實例化
() 包括 -- AD class 得到 rgs C# col 類中可以定義的成員,包括字段、屬性、構造函數、實例方法和析構函數等。 要訪問這些實例成員,必須通過類的實例對象來完成。而要得到一個類的實例對象,就必須先聲明一個該類類型的變量,然後使用new運算符後