基礎備忘:細說new與malloc的10點區別
正文
回到頂部前言
幾個星期前去面試C++研發的實習崗位,面試官問了個問題:
new與malloc有什麼區別?
這是個老生常談的問題。當時我回答new從自由儲存區上分配記憶體,malloc從堆上分配記憶體;new/delete會呼叫建構函式/解構函式對物件進行初始化與銷燬;operator new/delete可以進行過載;然後強行分析了一下自由儲存區與堆的區別。回來後感覺這個問題其實回答得不怎麼好,因為關於new與malloc的區別實際上很多。面試期間剛好是剛期末考完,之後是幾個課設沒時間去整理。今天花了點時間整理下這個問題。
回到頂部new與malloc的10點區別
1. 申請的記憶體所在位置
new操作符從自由儲存區(free store)上為物件動態分配記憶體空間,而malloc函式從堆上動態分配記憶體。自由儲存區是C++基於new操作符的一個抽象概念,凡是通過new操作符進行記憶體申請,該記憶體即為自由儲存區。而堆是作業系統中的術語,是作業系統所維護的一塊特殊記憶體,用於程式的記憶體動態分配,C語言使用malloc從堆上分配記憶體,使用free釋放已分配的對應記憶體。
那麼自由儲存區是否能夠是堆(問題等價於new是否能在堆上動態分配記憶體),這取決於operator new 的實現細節。自由儲存區不僅可以是堆,還可以是靜態儲存區,這都看operator new在哪裡為物件分配記憶體。
特別的,new甚至可以不為物件分配記憶體!定位new的功能可以辦到這一點:
new (place_address) type
place_address為一個指標,代表一塊記憶體的地址。當使用上面這種僅以一個地址呼叫new操作符時,new操作符呼叫特殊的operator new,也就是下面這個版本:
void * operatornew (size_t,void *) //不允許重定義這個版本的operator new
這個operator new不分配任何的記憶體,它只是簡單地返回指標實參,然後右new表示式負責在place_address指定的地址進行物件的初始化工作。
2.返回型別安全性
new操作符記憶體分配成功時,返回的是物件型別的指標,型別嚴格與物件匹配,無須進行型別轉換,故new是符合型別安全性的操作符。而malloc記憶體分配成功則是返回void * ,需要通過強制型別轉換將void*指標轉換成我們需要的型別。
型別安全很大程度上可以等價於記憶體安全,型別安全的程式碼不會試圖方法自己沒被授權的記憶體區域。關於C++的型別安全性可說的又有很多了。
3.記憶體分配失敗時的返回值
new記憶體分配失敗時,會丟擲bac_alloc異常,它不會返回NULL;malloc分配記憶體失敗時返回NULL。
在使用C語言時,我們習慣在malloc分配記憶體後判斷分配是否成功:
int *a = (int *)malloc ( sizeof (int ));
if(NULL == a)
{
...
}
else
{
...
}
從C語言走入C++陣營的新手可能會把這個習慣帶入C++:
int * a = new int();
if(NULL == a)
{
...
}
else
{
...
}
實際上這樣做一點意義也沒有,因為new根本不會返回NULL,而且程式能夠執行到if語句已經說明記憶體分配成功了,如果失敗早就拋異常了。正確的做法應該是使用異常機制:
try
{
int *a = new int();
}
catch (bad_alloc)
{
...
}
4.是否需要指定記憶體大小
使用new操作符申請記憶體分配時無須指定記憶體塊的大小,編譯器會根據型別資訊自行計算,而malloc則需要顯式地指出所需記憶體的尺寸。
class A{...}
A * ptr = new A;
A * ptr = (A *)malloc(sizeof(A)); //需要顯式指定所需記憶體大小sizeof(A);
當然了,我這裡使用malloc來為我們自定義型別分配記憶體是不怎麼合適的,請看下一條。
5.是否呼叫建構函式/解構函式
使用new操作符來分配物件記憶體時會經歷三個步驟:
- 第一步:呼叫operator new 函式(對於陣列是operator new[])分配一塊足夠大的,原始的,未命名的記憶體空間以便儲存特定型別的物件。
- 第二步:編譯器執行相應的建構函式以構造物件,併為其傳入初值。
- 第三部:物件構造完成後,返回一個指向該物件的指標。
使用delete操作符來釋放物件記憶體時會經歷兩個步驟:
- 第一步:呼叫物件的解構函式。
- 第二步:編譯器呼叫operator delete(或operator delete[])函式釋放記憶體空間。
總之來說,new/delete會呼叫物件的建構函式/解構函式以完成物件的構造/析構。而malloc則不會。如果你不嫌囉嗦可以看下我的例子:
class A
{
public:
A() :a(1), b(1.11){}
private:
int a;
double b;
};
int main()
{
A * ptr = (A*)malloc(sizeof(A));
return 0;
}
在return處設定斷點,觀看ptr所指記憶體的內容:
可以看出A的預設建構函式並沒有被呼叫,因為資料成員a,b的值並沒有得到初始化,這也是上面我為什麼說使用malloc/free來處理C++的自定義型別不合適,其實不止自定義型別,標準庫中凡是需要構造/析構的型別通通不合適。
而使用new來分配物件時:
int main()
{
A * ptr = new A;
}
檢視程式生成的彙編程式碼可以發現,A的預設建構函式被呼叫了:
6.對陣列的處理
C++提供了new[]與delete[]來專門處理陣列型別:
A * ptr = new A[10];//分配10個A物件
使用new[]分配的記憶體必須使用delete[]進行釋放:
delete [] ptr;
new對陣列的支援體現在它會分別呼叫建構函式函式初始化每一個數組元素,釋放物件時為每個物件呼叫解構函式。注意delete[]要與new[]配套使用,不然會找出陣列物件部分釋放的現象,造成記憶體洩漏。
至於malloc,它並知道你在這塊記憶體上要放的陣列還是啥別的東西,反正它就給你一塊原始的記憶體,在給你個記憶體的地址就完事。所以如果要動態分配一個數組的記憶體,還需要我們手動自定陣列的大小:
int * ptr = (int *) malloc( sizeof(int)* 10 );//分配一個10個int元素的陣列
7.new與malloc是否可以相互呼叫
operator new /operator delete的實現可以基於malloc,而malloc的實現不可以去呼叫new。下面是編寫operator new /operator delete 的一種簡單方式,其他版本也與之類似:
void * operatornew (sieze_t size)
{
if(void * mem = malloc(size)
return mem;
else
throw bad_alloc();
}
voidoperatordelete(void *mem) noexcept{
free(mem);
}
8.是否可以被過載
opeartor new /operator delete可以被過載。標準庫是定義了operator new函式和operator delete函式的8個過載版本:
//這些版本可能丟擲異常
void * operatornew(size_t);
void * operator new[](size_t);
void * operatordelete (void * )noexcept;
void * operator delete[](void *0)noexcept;
//這些版本承諾不丟擲異常
void * operatornew(size_t ,nothrow_t&) noexcept;
void * operator new[](size_t, nothrow_t& );
void * operatordelete (void *,nothrow_t& )noexcept;
void * operator delete[](void *0,nothrow_t& )noexcept;
我們可以自定義上面函式版本中的任意一個,前提是自定義版本必須位於全域性作用域或者類作用域中。太細節的東西不在這裡講述,總之,我們知道我們有足夠的自由去過載operator new /operator delete ,以決定我們的new與delete如何為物件分配記憶體,如何回收物件。
而malloc/free並不允許過載。
9. 能夠直觀地重新分配記憶體
使用malloc分配的記憶體後,如果在使用過程中發現記憶體不足,可以使用realloc函式進行記憶體重新分配實現記憶體的擴充。realloc先判斷當前的指標所指記憶體是否有足夠的連續空間,如果有,原地擴大可分配的記憶體地址,並且返回原來的地址指標;如果空間不夠,先按照新指定的大小分配空間,將原有資料從頭到尾拷貝到新分配的記憶體區域,而後釋放原來的記憶體區域。
new沒有這樣直觀的配套設施來擴充記憶體。
10. 客戶處理記憶體分配不足
在operator new丟擲異常以反映一個未獲得滿足的需求之前,它會先呼叫一個使用者指定的錯誤處理函式,這就是new-handler。new_handler是一個指標型別:
namespace std
{
typedefvoid (*new_handler)();
}
指向了一個沒有引數沒有返回值的函式,即為錯誤處理函式。為了指定錯誤處理函式,客戶需要呼叫set_new_handler,這是一個聲明於的一個標準庫函式:
namespace std
{
new_handler set_new_handler(new_handler p ) throw();
}
set_new_handler的引數為new_handler指標,指向了operator new 無法分配足夠記憶體時該呼叫的函式。其返回值也是個指標,指向set_new_handler被呼叫前正在執行(但馬上就要發生替換)的那個new_handler函式。
對於malloc,客戶並不能夠去程式設計決定記憶體不足以分配時要幹什麼事,只能看著malloc返回NULL。
回到頂部總結
將上面所述的10點差別整理成表格:
特徵 | new/delete | malloc/free |
---|---|---|
分配記憶體的位置 | 自由儲存區 | 堆 |
記憶體分配成功的返回值 | 完整型別指標 | void* |
記憶體分配失敗的返回值 | 預設丟擲異常 | 返回NULL |
分配記憶體的大小 | 由編譯器根據型別計算得出 | 必須顯式指定位元組數 |
處理陣列 | 有處理陣列的new版本new[] | 需要使用者計算陣列的大小後進行記憶體分配 |
已分配記憶體的擴充 | 無法直觀地處理 | 使用realloc簡單完成 |
是否相互呼叫 | 可以,看具體的operator new/delete實現 | 不可呼叫new |
分配記憶體時記憶體不足 | 客戶能夠指定處理函式或重新制定分配器 | 無法通過使用者程式碼進行處理 |
函式過載 | 允許 | 不允許 |
建構函式與解構函式 | 呼叫 | 不呼叫 |
malloc給你的就好像一塊原始的土地,你要種什麼需要自己在土地上來播種
而new幫你劃好了田地的分塊(陣列),幫你播了種(建構函式),還提供其他的設施給你使用:
當然,malloc並不是說比不上new,它們各自有適用的地方。在C++這種偏重OOP的語言,使用new/delete自然是更合適的。
相關推薦
基礎備忘:細說new與malloc的10點區別
正文 回到頂部 前言 幾個星期前去面試C++研發的實習崗位,面試官問了個問題: new與malloc有什麼區別? 這是個老生常談的問題。當時我回答new從自由儲存區上分配記憶體,malloc從堆上分配記憶體;new/delete會呼叫建構函式/解構函式對
備忘:numpy中的matrix與array的區別
參考:https://www.cnblogs.com/cymwill/p/7823148.html Numpy matrices必須是2維的,但是 numpy arrays (ndarrays) 可以是多維的(1D,2D,3D····ND). Matrix是Arr
neon 的常見彙編指令與程式碼對照【基礎備忘】
作者:歌神flaming 來源:CSDN 原文:https://blog.csdn.net/u010684585/article/details/78455993 .arm .text .global cost_init cost_i
Hadoop HDFS bug備忘:DN的刪除佇列與block report的協調引起的資料丟失
現象描述:線上任務出現失敗,日誌中顯示查詢計算用的block失敗。 現象分析:NN記錄了block的所在的DN,但是DN上卻沒有了這個block;通常,刪除過程是先刪除NN\DN上的資料,然後再刪除磁碟上的資料。 肯定是某種條件,使得NN恢復了block資訊,而DN上的確刪
ajax基礎------備忘
user odi blog www action writer word nal urlencode 1:register.jsp <%@ page language="java" contentType="text/html; charset=UTF-8"
備忘:Junit單元測試
環境 包名 args 方法 成功 junit單元測試 備忘 [] 命名 junit 目前測試都是在main方法中調用目前的結果都需要人工對比是否是想要的 1.使用Junit測試方法,綠色條條代表方法測試成功,沒有bug,如果是紅色條條代表有異常,測試不通過2.點擊方法名、類
C++:new與malloc的區別
函數 rim 自定義類 對象分配 文件 而是 計算 動態申請 成功 1.屬性 new/delete是操作符,是C++關鍵字,需要編譯器支持;malloc/free是庫函數,需要頭文件支持。 2.參數 使用new操作符動態分配內存時無需指定內存塊大小,編譯器會根據類型自行計算
css備忘:id選擇器的權重>class選擇器的權重=屬性選擇器的權重>元素選擇器
基本 分享 bubuko col clas AS -c 測試 com 最近的項目要自己寫前端了,重新學習下前端的一下基本知識。 一般在css樣式表中,上面的會被下面的覆蓋,如下圖,文字會顯示藍色: 所以按照正常的來說,下面的css樣式,測試的文字應該還是藍色 但結果,測
基礎運維:系統啟動與關閉順序、啟動級別
空間 第一個 例如 alt mage log ystemd 循環 image 一、系統啟動的順序分為如下: BIOS加電自檢--》引導裝載程序--》內核初始化--》啟動父進程systemd 二、系統啟動順序狀態: 1.系統處於完成關機狀態2.系統處於開機狀態,使用命令
C#:abstarct 和 virtual關鍵字的區別分析,new 與 override關鍵字區別分析
1.首先 absratct 和 virtual 都是與繼承概念有關係。 2.區別 ① virtual 關鍵字用於在父類中修飾方法,該方法在父類中可提供實現,並且子類中使用此方法有兩種方式。 方式1: 在父類中定義了virtual方法,在子類中沒有用ov
Django基礎(1:架構流程與目錄)
Django 是 python 做 web 開發的一個很著名的框架,很多大型網站(比如豆瓣、YouTube)都用它開發,下面就來介紹一下 Django 的架構原理 原理 1. MVT設計模式 M(Models):資料模型,直接對映到資料庫
MyBatis備忘:查詢資料庫實現登入功能的一個演示
複習:entity DAO/DBHelper service action jsp 總結:使用mybatis後的區別:DBHelper裡書寫的是SqlSession,而不是自己來拿資料庫連線了,當然這得寫配置檔案SqlMapConfig;serv
C語言基礎知識筆記:day4字串與字元陣列
注:本筆記為直接上傳,因各個markdown筆記語法的差異性,在顯示上略有區別。 如需原版請聯絡:[email protected]。(郵件主題為:學習筆記,正文需要的筆記名,可以直接複製該筆記的網址)。同時歡迎各位一起學習交流。 day4字串與字元陣
JS 基礎篇(六):事件冒泡與捕獲
目錄: 一、事件冒泡 微軟提出了名為事件冒泡(event bubbling)的事件流。事件冒泡可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。也就是說,事件會從最內層的元素開始發生,一直向上傳播,直到document物件。 <html>
spring常用註解備忘: @Configuration、@ConfigurationProperties、@Bean、@Qualifier
1,下面先給出這四個註解的使用場景程式碼: A,這四個註解的使用如下: /** * 卡牛徵信 */ @Configuration @ConfigurationProperties(prefix="cube.partner.conifg.kn") public clas
DSP 基礎學習 1:搭建環境與CCS6建立工程模板
DSP 基礎學習 1:搭建環境與CCS6建立工程模板 1 DSP簡介 1.1 DSP 發展 1.2 DSP 應用領域 1.3 DSP 產品現狀 1.4 哈佛結構 1.5 TMS320F28335 簡介 2 搭
NEON 指令集【基礎備忘】
轉自 http://blog.sina.com.cn/s/blog_602f87700102wj5w.html 作者:Sam (甄峰) [email protected] 之前在一些ARM CPU下,曾在編譯時指定過Neon。
備忘:ehcache的簡單使用說明
ehcache 中的主要概念 CacheManager:快取管理器。負責Cache的建立、訪問、移除。 Cache:快取例項。所有的cache都實現了介面Ehcache。每個cache都有名字和屬性,且包含N個Element。 Element:存放於Cach
程式碼備忘:常用SQL
1、CASE語句 CASE WHEN <A> THEN <somethingA> WHEN <B> THEN <somethingB> ELSE <somethingE> END 舉例: SELECT
近日有需要寫點C#程式,有用到Dataset資料集和SQLite資料庫,由於我從來就不擅長記各種程式語言的語法,所以在查閱一堆資料後,留下以下內容備忘: 一、SQLite操作,直接貼程式碼,很簡單
近日有需要寫點C#程式,有用到Dataset資料集和SQLite資料庫,由於我從來就不擅長記各種程式語言的語法,所以在查閱一堆資料後,留下以下內容備忘: 一、SQLite操作,直接貼程式碼,很簡單: //建立一個數據庫檔案 string d