new/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 和 delete 是不允許過載的。但我還沒搞清楚為什麼要用 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 *pA = new A(10);
來建立一個類的物件,返回其指標 pA。如下圖所示 new 背後完成的工作:
簡單總結一下:
- 首先需要呼叫上面提到的 operator new 標準庫函式,傳入的引數為 class A 的大小,這裡為 8 個位元組,至於為什麼是 8 個位元組,你可以看看《深入 C++ 物件模型》一書,這裡不做多解釋。這樣函式返回的是分配記憶體的起始地址,這裡假設是 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 個位元組用來儲存陣列的大小,但是最終返回的是物件陣列的指標,而不是所有分配空間的起始地址。
這樣的話,釋放就很簡單了:
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[] 要配套使用總是沒錯的!
相關推薦
new/delete 和 new[]/delete[] 配套使用
new 和 delete 到底是什麼? 如果找工作的同學看一些面試的書,我相信都會遇到這樣的題:sizeof 不是函式,然後舉出一堆的理由來證明 sizeof 不是函式。在這裡,和 sizeof 類似,new 和 delete 也不是函式,它們都是 C++ 定義的關鍵字
淺談 C++ 中的 new/delete 和 new[]/delete[]
在 C++ 中,你也許經常使用 new 和 delete 來動態申請和釋放記憶體,但你可曾想過以下問題呢? new 和 delete 是函式嗎? new [] 和 delete [] 又是什麼?什麼時候用它們? 你知道 operator new 和 operator delete 嗎? 為什麼
關於C++中new/delete和new[]/delete[]
參看連結 淺談 C++ 中的 new/delete 和 new[]/delete[] operator new 和 operator delete 這兩個其實是 C++ 語言標準庫的庫函式,原型分別如下: void *operator new(size_t); //al
C++中的new/delete和new[]/delete[]
1、瞭解new-handler的行為 通俗來講就是new失敗的時候呼叫的回撥函式,直接看程式碼: #include<iostream> #include<string.h> #include <stdlib.h> using nam
new/delete 和new[]/delete[]的解讀(轉)
new 和 delete 到底是什麼? 如果找工作的同學看一些面試的書,我相信都會遇到這樣的題:sizeof 不是函式,然後舉出一堆的理由來證明 sizeof 不是函式。在這裡,和 sizeof 類似,new 和 delete 也不是函式,它們都是 C++ 定義
PHP中new static() 和 new self() 的區別
pub 堆內存 func sel urn ret 通過 ati php self 指的是self所在的類 new static 實例化的是當前使用的類,有點像$this ,從堆內存中提取出來。 還是通過實例說明一下: class A { public static f
php -- new self() 和 new static
如果 color fun span pre clas stat 類繼承 extends 看一段摘自網上的代碼 class A { public static function get_self() { return new self(); } pu
PHP new self()和new static()的區別
phpnew static()是php5.3以後引入新的特性,延遲靜態綁定.訪問的是當前實例化的那個類,那麽 static 代表的就是那個類。new self() 是指的不是調用上下文,它指的是解析上下文.class Test { public static funtion getSelf(){
new self() 和 new static() 的區別
1、new static()是在php5.3版本引入的新特性 2、無論是 new static 還是 new self() 都是 new 一個物件 3、這兩個方法new 出來的物件 有什麼區別呢?說白了就是new出來的到底是同一個類的實列還是不同類的實列 為了探究上面的問
PHP中new self()和new static()的區別--延遲靜態載入
1.new static()是在PHP5.3版本中引入的新特性。 2.無論是new static()還是new self(),都是new了一個新的物件。 3.這兩個方法new出來的物件有什麼區別呢,說白了就是new出來的到底是同一個類例項還是不同的類例項呢? 為了探究上面的問題,我們
new static 和 new self的區別
new static new self 都是例項化當前類, 但是new static只有程式碼所在的類,就是子類如果沒有重寫的話那麼例項化的就是父類。 而ne
c++學習之new int()和new int[]的區別
new int[] 是建立一個int型陣列,陣列大小是在[]中指定,例如: int * p = new int[3]; //申請一個動態整型陣列,陣列的長度為[]中的值 new int()是建立一個
PHP的self::與static::,new self()和new static()之分
//後期靜態繫結 class A { public static function who() { var_dump("I'm A"); } public static function test() { self::who();
【PHP趣味】new self和new static的區別
<?php class Test { private $_user; protected function __construct($user) { $this->_user = $user; } public
Java中"throw new Exception() "和"new Exception()"的區別
throw new Exception(String, Exception) throw new Exception(String) throw是明確地丟擲異常 ////throws的作用/////////////////////////////////// 宣告方法可能
C/C++ - malloc/free和new/delete的區分
字節 delete 分別是 自定義 void int eight 構造函數 內存 new/delete與malloc/free的區別主要表現在以下幾個方面: 註意:最主要的區別,new/delete是運算符,而malloc/free是函數 (1)、new能夠自動計算
淺談new/delete和malloc/free的用法與區別
淺談new/delete和malloc/free的用法與區別 目錄 一.new和delete用法 二.malloc和free的用法 三.new和malloc的區別 正文 每個程式在執行時都會佔用一塊可用的記憶體空間,
過載operator new和 operator delete
1.建立類Foo 在類Foo中過載operator new和 operator delete。 class Foo { private: int _id; public: Foo() :_id(0) { cout << "De
C和C++的區別:new /delete 和 malloc/free
幕布分享: https://mubu.com/doc/vQfZHGsDG0 動態分配記憶體: 在程式執行中進行的,而不是在編譯就確定的 new 堆上分配記憶體 (1) 開闢T位元組大小空間: Tp =new T; size: sizeof(T)  
malloc/free和new/delete的區別
malloc與free是C++/C語言的標準庫函式,new/delete是C++的運算子