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的用法:

1. new() 分配這種型別的一個大小的記憶體空間,並以括號中的值來初始化這個變數;

2. new[] 分配這種型別的n個大小的記憶體空間,並用預設建構函式來初始化這些變數;


例子:

#include

#include

using namespace std;

int main(){

char * p=new char("Hello");

//error分配一個char(1位元組)的空間,

//用"Hello"來初始化,這明顯不對



char* p=new char[6];

//p="Hello";

//不能將字串直接賦值給該字元指標p,原因是:

//指標p指向的是字串的第一個字元,只能用下面的

//strcpy

strcpy(p,"Hello");

cout<<*p<<endl; //只是輸出p指向的字串的第一個字元!

cout<<p<<endl; //輸出p指向的字串!

delete[] p;

return 0;

}

輸出結果:

H

Hello

3.開闢單變數地址空間

1)new int; //開闢一個存放陣列的儲存空間,返回一個指向該儲存空間的地址.int *a = new int 即為將一個int型別的地址賦值給整型指標a.

2)int *a = new int(5) 作用同上,但是同時將整數賦值為5

4.開闢陣列空間

一維: int *a = new int[100];開闢一個大小為100的整型陣列空間

二維: int **a = new int[5][6]

三維及其以上:依此類推.

一般用法: new 型別 [初值]

5. 當使用new運算子定義一個多維陣列變數或陣列物件時,它產生一個指向陣列第一個元素的指標,返回的型別保持了除最左邊維數外的所有維數。例如:

int *p1 = new int[10];

返回的是一個指向int的指標int*

int (*p2)[10] = new int[2][10];

new了一個二維陣列, 去掉最左邊那一維[2], 剩下int[10], 所以返回的是一個指向int[10]這種一維陣列的指標int (*)[10].

int (*p3)[2][10] = new int[5][2][10]; new了一個三維陣列, 去掉最左邊那一維[5], 還有int[2][10], 所以返回的是一個指向二維陣列int[2][10]這種型別的指標int (*)[2][10].



new運算子

最常用的是作為運算子的new,比如:

string *str = new string(“test new”);

作為運算子,new和sizeof一樣,是C 內建的,你不能對它做任何的改變,除了使用它。

new會在堆上分配一塊記憶體,並會自動呼叫類的建構函式。

new函式

第二種就是new函式,其實new運算子內部分配記憶體使用的就是new函式,原型是:

void *operator new(size_t size);

new函式返回的是一個void指標,一塊未經初始化的記憶體。如你所見,這和C語言的malloc行為相似,你可以過載new函式,並且增加額外的引數,但是必須保證第一個引數必須是size_t型別,它指明瞭分配記憶體塊的大小,C 允許你這麼做,當然一般情況下這是不必要的。如果過載了new函式,在使用new操作符時呼叫的就是你過載後的new函數了。

如果使用new函式,和語句string *str = new string(“test new”)相對的程式碼大概是如下的樣子:

1. string *str = (string*)operator new(sizeof(string));

2. str.string(“test new”);

3. // 當然這個呼叫時非法的,但是編譯器是沒有這個限制的

這還不算完,還有第三種的new存在。

placement new

第三種,placement new,這也是new作為函式的一種用法,它允許你在一塊已存在的記憶體上分配一個物件,而記憶體上的資料不會被覆蓋或者被你主動改寫,placement new同樣由new操作符呼叫,呼叫格式是:

new (buffer) type(size_t size);

先看看下面的程式碼:

4. char str[22];

5. int data = 123;

6. int *pa = new (&data) int;

7. int *pb = new (str) int(9);

結果*pa = 123(未覆蓋原資料),而*pb = 9(覆蓋原資料),可以看到placement new 並沒有分配新的記憶體,也可以使用在棧上分配的記憶體,而不限於堆。

為了使用placement new 你必須包含或者

其實placement new和第二種一樣,只不過多了引數,是函式new的過載,語法格式為:

void *operator new(size_t, void* buffer);

它看起來可能是這個樣子:

void *operator new(size_t, void* buffer) { return buffer;}

和new對應的就是delete了,需要回收記憶體啊,不然就洩漏了,這個下次再寫吧,回憶一下今天的內容先。

二.delete用法:

1. int *a = new int;

delete a; //釋放單個int的空間

2.int *a = new int[5];

delete [] a; //釋放int陣列空間

要訪問new所開闢的結構體空間,無法直接通過變數名進行,只能通過賦值的指標進行訪問.

用new和delete可以動態開闢,撤銷地址空間.在程式設計序時,若用完一個變數(一般是暫時儲存的陣列),下次需要再用,但卻又想省去重新初始化的功夫,可以在每次開始使用時開闢一個空間,在用完後撤銷它.



總結

1. 函式new

void *operator new(size_t size); 在堆上分配一塊記憶體,和placement new(void *operator new(size_t, void* buffer)); 在一塊已經存在的記憶體上建立物件,如果你已經有一塊記憶體,placement new會非常有用,事實上,它STL中有著廣泛的使用。

2. 運算子new

最常用的new,沒什麼可說的。

3. 函式new不會自動呼叫類的建構函式,因為它對分配的記憶體型別一無所知;而運算子new會自動呼叫類的建構函式。

4. 函式new允許過載,而運算子new不能被過載。

【1】malloc與free  和 new與delete

(1)malloc與free是C++/C語言的標準庫函式。new/delete是C++的運算子。它們都可以申請動態記憶體和釋放記憶體。

(2)對於非內部資料型別的物件而言,用malloc/free無法滿足動態物件的要求(物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式)。

(3)由於malloc/free是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。因此,C++語言需要可以完成動態記憶體分配和初始化工作的運算子new,以及一個能完成清理與釋放記憶體工作的運算子delete。

(4)都是在堆(heap)上進行動態的記憶體操作。用malloc函式需要指定記憶體分配的位元組數並且不能初始化物件。new會自動呼叫物件的建構函式。

delete 會呼叫物件的destructor,而free 不會呼叫物件的destructor。

【2】描述記憶體分配方式以及它們的區別?

(1)從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如:全域性變數,static 變數。

(2)在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集。

(3)從堆上分配(動態記憶體分配)。程式在執行的時候用malloc 或 new 申請任意多少的記憶體,程式設計師自己負責在何時用free 或delete 釋放記憶體。

動態記憶體的生存期由程式設計師決定,使用非常靈活,但問題也最多。

【3】malloc 與 free

(1)malloc函式分配的空間一定要用free函式釋放掉。

(2)free(p) 僅僅指釋放了malloc分配的空間,但是p指標仍然不為空,所以,在free函式釋放後一般要置空!防止野指標!

(3)非空指標只可以釋放一次。

(4)一般兩者搭配使用。

(5)malloc 與 free示例程式碼如下:

複製程式碼
 1 #include<iostream>
 2 #include<malloc.h>
 3 using namespace std;
 4 
 5 void main()
 6 {
 7     int *p = NULL ;  //指標定義最好初始化為空(程式設計師基本素養,哈哈)
 8 
 9     //空指標釋放多次沒有任何意義
10     free(p);   //編譯通過!執行通過!一次

            
           

相關推薦

new/delete new[]/delete[]的解讀

 new 和 delete 到底是什麼? 如果找工作的同學看一些面試的書,我相信都會遇到這樣的題:sizeof 不是函式,然後舉出一堆的理由來證明 sizeof 不是函式。在這裡,和 sizeof 類似,new 和 delete 也不是函式,它們都是 C++ 定義

數據結構-深度遍歷廣度遍歷

指針 void 邊表 當前 初始化 循環隊列 logs == ont 本文轉自http://blog.csdn.net/wingofeagle/article/details/13020373 深度遍歷: 從圖中某個頂點v出發,訪問此頂點,然後從v的未被訪問的鄰接點出發

spring jar包解讀

hiberna 調用 緩存 ont 設備 校驗 工具 transacti 代碼 作者:http://www.cnblogs.com/leehongee/archive/2012/10/01/2709541.html spring.jar 是包含有完整發布模塊的單個jar 包

MySql之ALTER命令用法詳細解讀

修改表 pre const 命令使用 add ear 修改 blog rain 本文詳細解讀了MySql語法中Alter命令的用法,這是一個用法比較多的語法,而且功能還是很強大的。 USE learning;(自己要提前建好) CREATE TABLE student

Spring3 MVC 註解---註解基本配置及@controller @RequestMapping 常用解釋

nal context pac 配置 註解 com inf 如何 文件中 一:配置web.xml 1)問題:spring項目中有多個配置文件mvc.xml dao.xml 2)解決:在web.xml中 <init-par

Java中常量定義在interfaceclass的區別

var tac 不能被繼承 ble -o err 模式 variable 個人愛好 最終結論:定義常量在interface和class中其實都行,關鍵是看你的設計和個人愛好。 Java中interface中定義變量默認都是"public static final"類型的,

JFrameJInternalFrame示例學習

splay wid and eat oca nds etc idt bar 1. 創建主窗體並簡單建立菜單項,示例代碼如下: package internalFrame; import javax.swing.JInternalFrame; import javax.s

HTTPSHTTP的區別

cap 解決 加密方法 nbsp 快速 之間 cape 而不是 銀行 什麽是 HTTPS? HTTPS (基於安全套接字層的超文本傳輸協議 或者是 HTTP over SSL) 是一個 Netscape 開發的 Web 協議。 你也可以說:HTTPS = HTTP + SS

Java中的代碼點代碼單元

swing enter 錯誤 字體 消息 關系 小文本 開發人員 界面 文章來源:http://blog.csdn.net/weizhaozhe/article/details/3909079 這篇文章講的很細,但是對於初學者也很難理解,在後面的筆記中,我會陳述自己的簡單

URIURL的區別

ado 網絡資源 時也 fontsize 能夠 ren 文檔 自身 p地址 1說明: 這段時間寫android的時候用到了URL和URI,有點分不清楚,於是做了一個系統性的學習。在這裏將自己的學習筆記粘貼出來,希望對大家有幫助。 1)Java類庫裏有兩個對應的類java.n

Servlet中forwardredirect的區別

intern host 邏輯 overflow IT lan 實時性 解析 relative forward方式:request.getRequestDispatcher("/somePage.jsp").forwardrequest, response); red

Python selenium —— 一定要會用selenium的等待,三種等待方式解讀

我們 嚴重 -s ber 約定 fire locate ror nbsp 發現太多人不會用等待了,博主今天實在是忍不住要給大家講講等待的必要性。 很多人在群裏問,這個下拉框定位不到、那個彈出框定位不到…各種定位不到,其實大多數情況下就是兩種問題:1 有frame,2 沒有加

AIX文件系統存儲部署

log 日常 修改 語法 使用 顯示文件 chl 由於 系統 文件系統和存儲部署 文件系統的管理是AIX存儲結構中的最後一環。定義完lv後,可采用如下兩種方式使用lv: a.作為裸設備(raw)使用,一般是數據庫型的應用 b.在lv上定義文件系統,並提供文件和數據的存儲服務

hadoop學習筆記:hdfs體系結構讀寫流程

sim 百萬 服務器 發表 繼續 什麽 lose 基於 一次 原文:https://www.cnblogs.com/codeOfLife/p/5375120.html 目錄 HDFS 是做什麽的 HDFS 從何而來 為什麽選擇 HDFS 存儲數據 HDFS

Win7+Ubuntu雙系統結構下,Ubuntu克隆至新硬碟,啟動成功 Linux下檢視硬碟UUID修改硬碟UUID

前言梗概: 750GB 機械硬碟下安裝 Win7 和 Ubuntu雙系統,之前Win7單獨重灌後,將grub引導覆蓋;利用EasyBCD恢復Ubuntu啟動。 最近發現之前Ubuntu分配空間太小,想將其安裝到一個大一點的SSD(固態硬碟)上。 Ubuntu情況如下, /dev/s

java中Integerint的區別

prev 指針 引用 .com 地址 區別 val details sdn int和Integer的區別 1、Integer是int的包裝類,int則是java的一種基本數據類型 2、Integer變量必須實例化後才能使用,而int變量不需要 3、Integer實際是對象

MingWcygwin的區別

個人總結:讀完這段文字需要5分支 總結: MingW是一個編譯器 https://zh.wikipedia.org/wiki/MinGW   Cygwin是一組套件 https://zh.wikipedia.org/wiki/Cygwin   MingW和cygwin的區別

MemCache詳細解讀

參考:https://www.cnblogs.com/xrq730/p/4948707.html MemCache是什麼 MemCache是一個自由、原始碼開放、高效能、分散式的分散式記憶體物件快取系統,用於動態Web應用以減輕資料庫的負載。它通過在記憶體中快取資料和物件來減少讀取資料庫的次數,從而提高了

C語言中,標頭檔案原始檔的關係

//a.h void foo(); //a.c #include "a.h"   //我的問題出來了:這句話是要,還是不要? void foo() {      return; } //main.c #include "a.h" int main(int argc

最詳細的Faster RCNN解讀

mark一下,感謝作者分享! https://zhuanlan.zhihu.com/p/31426458 經過R-CNN和Fast RCNN的積澱,Ross B. Girshick在2016年提出了新的Faster R-CNN,在結構上,Faster RCNN已經將特徵