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的用法:
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中常量定義在interface和class的區別(轉)
var tac 不能被繼承 ble -o err 模式 variable 個人愛好 最終結論:定義常量在interface和class中其實都行,關鍵是看你的設計和個人愛好。
Java中interface中定義變量默認都是"public static final"類型的,
JFrame和JInternalFrame示例學習(轉)
splay wid and eat oca nds etc idt bar 1. 創建主窗體並簡單建立菜單項,示例代碼如下:
package internalFrame;
import javax.swing.JInternalFrame;
import javax.s
HTTPS和HTTP的區別(轉)
cap 解決 加密方法 nbsp 快速 之間 cape 而不是 銀行 什麽是 HTTPS?
HTTPS (基於安全套接字層的超文本傳輸協議 或者是 HTTP over SSL) 是一個 Netscape 開發的 Web 協議。
你也可以說:HTTPS = HTTP + SS
Java中的代碼點和代碼單元(轉)
swing enter 錯誤 字體 消息 關系 小文本 開發人員 界面
文章來源:http://blog.csdn.net/weizhaozhe/article/details/3909079
這篇文章講的很細,但是對於初學者也很難理解,在後面的筆記中,我會陳述自己的簡單
URI和URL的區別(轉)
ado 網絡資源 時也 fontsize 能夠 ren 文檔 自身 p地址 1說明:
這段時間寫android的時候用到了URL和URI,有點分不清楚,於是做了一個系統性的學習。在這裏將自己的學習筆記粘貼出來,希望對大家有幫助。
1)Java類庫裏有兩個對應的類java.n
Servlet中forward和redirect的區別(轉)
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中Integer和int的區別(轉)
prev 指針 引用 .com 地址 區別 val details sdn
int和Integer的區別
1、Integer是int的包裝類,int則是java的一種基本數據類型 2、Integer變量必須實例化後才能使用,而int變量不需要 3、Integer實際是對象
MingW和cygwin的區別(轉)
個人總結:讀完這段文字需要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已經將特徵