1. 程式人生 > >new/delete 和 new[]/delete[] 配套使用

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 背後完成的工作:

簡單總結一下:

  1. 首先需要呼叫上面提到的 operator new 標準庫函式,傳入的引數為 class A 的大小,這裡為 8 個位元組,至於為什麼是 8 個位元組,你可以看看《深入 C++ 物件模型》一書,這裡不做多解釋。這樣函式返回的是分配記憶體的起始地址,這裡假設是 0x007da290。
  2. 上面分配的記憶體是未初始化的,也是未型別化的,第二步就在這一塊原始的記憶體上對類物件進行初始化,呼叫的是相應的建構函式,這裡是呼叫 A:A(10); 這個函式,從圖中也可以看到對這塊申請的記憶體進行了初始化,var=10, file 指向開啟的檔案
  3. 最後一步就是返回新分配並構造好的物件的指標,這裡 pA 就指向 0x007da290 這塊記憶體,pA 的型別為類 A 物件的指標。

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

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

delete pA;

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

delete 就做了兩件事情:

  1. 呼叫 pA 指向物件的解構函式,對開啟的檔案進行關閉。
  2. 通過上面提到的標準庫函式 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/deletenew[]/delete[]

參看連結 淺談 C++ 中的 new/delete 和 new[]/delete[] operator new 和 operator delete 這兩個其實是 C++ 語言標準庫的庫函式,原型分別如下: void *operator new(size_t); //al

C++中的new/deletenew[]/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 selfnew 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/freenew/delete的區分

字節 delete 分別是 自定義 void int eight 構造函數 內存 new/delete與malloc/free的區別主要表現在以下幾個方面:   註意:最主要的區別,new/delete是運算符,而malloc/free是函數   (1)、new能夠自動計算

淺談new/deletemalloc/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

CC++的區別:new /delete malloc/free

幕布分享: https://mubu.com/doc/vQfZHGsDG0 動態分配記憶體: 在程式執行中進行的,而不是在編譯就確定的 new 堆上分配記憶體 (1) 開闢T位元組大小空間: Tp =new T; size: sizeof(T)    

malloc/freenew/delete的區別

malloc與free是C++/C語言的標準庫函式,new/delete是C++的運算子