看板 C_and_CPP 關於我們 聯絡資訊
C 和 C++ 動態配置記憶體的差別? C 使用 malloc、free;而 C++ 使用 new、delete 另外,在 C++ 配置記憶體後,會額外呼叫建構子 (ctor); C++ 釋放記憶體前,會額外呼叫解構子 (dtor) 另外的另外的另外 new 與 delete 是運算子 (operator); 而 malloc 與 free 只是普通的函數 (ordinary function) 不重要的另外 是一元運算子,且為右結合性,且跟 prefix ++ 那群有相同的運算優先權。 稍微重要的另外: * 有人可能會講 operator new / new operator 這兩個很像的名詞 (如何分辨?) * 有內建版本 (built-in) 的 new/delete,也有非內建 (如何區分?) * placement new 或 placement syntax (如何使用?) 假設被雷打到暫時性失憶,阿母也來不及寄書給你查, 連到 MSDN new Operator (C++) 查線上說明,看 new 的語法為何: http://msdn.microsoft.com/en-us/library/kewsb8ba.aspx [::] new [placement] new-type-name [new-initializer] [::] new [placement] ( type-name ) [new-initializer] 中括號 [ 跟 ] 不是指陣列,表示可有可無,故有兩種選擇 ::new CMyClass; new CMyClass; 上面這兩個都是可以寫的,第一種表示強制呼叫 global 版,也就是內建版。 在 VC,這個內建版的 new 定義在檔案: C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\new.cpp void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc); (built-in non-placement operator new) 使用第二種寫法「new CMyClass;」時,會不會呼叫內建版, 要視 CMyClass::operator new(...) 是否存在而定。 而 new [placement] 表示是否使用 placement syntax。 new(123) CMyClass; new("Hello World", 456, 789) CMyClass; new( (void *) addr ) CMyClass; 上面三種 new 的後面都有接小括號,都使用了 placment syntax (語法)。 當程式中確實有 #include <new> 標頭檔時,就能使用 placement new。 至於 new new-type-name 跟 new ( type-name ) 是差不多的東西。 如果要動態建立「函數指標」型態,則因為函數指標本身含有小括號,所以 必須用 new ( type-name ) 語法,也就是寫成: new (void (*)(int, float)); 型態本身沒有小括號時,就用 new new-type-name 就好了。 至於 [new-initializer] 就是看要呼叫哪個建構子: new CFoo; new CFoo("blah"); 不要加 initializer 就把小括號拿掉,就會用預設建構子,沒什麼好說的。 operator new ≠ new operator 有時候會看到這兩個名詞,放在一起容易搞混,單獨出現你又要懷疑對方有沒有誤用。 吃完要拉,拉完要吃,人生就是慘啦 在 C++ Primer 書中,提到 new operator 時,都盡量講「new 算式」,也可講 new expression 或 new statement。 new 算式的行為是固定不變的,依序有三個步驟: (1) 呼叫相應的 operator new 函數,將返回值保存在 void *someAddr (2) 在 someAddr 位址,呼叫 contructor 建構物件 (3) 將 someAddr 的值傳給 new 算式的左方運算元 由此可見,如果 new operator 等於 operator new,那步驟2就不合邏輯了。 operator new 具體內容 operator new 有三大類: (1) 內建版:在 <new> 裡。即 placement operator new (2) 內建版:在 new.cpp 裡。即 non-placement operator new (3) 使用者自訂版 假設沒有使用者自訂,則當 new 算式沒有使用 placment 語法時, 比如寫「new double(3.14)」,則 VC 會呼叫函數 void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc); 此函數主要就是用 malloc 配置記憶體,並返回而已。 其參數 size 由編譯器自動算出,為物件所需大小。 而 placemnet operator new 是 inline 函數,在標頭檔 <new> 裡面, 就一行 return (_Where); 所以不常被人使用的 placement new 看似比較複雜, 其實對編譯器來說,只需要幫忙呼叫 ctor,然後把位址原封不動 傳回給 new 算式的左方運算元即可,簡單的工作。 operator new 函數,可以有很多種版本,也就是被 overload 成多種形式, 但其 parameter1 永遠是 size_t 型態,由編譯器負責傳入。 至於是否有 parameter2, parameter3, ..., parameterN 隨個人喜好。 舉例來說,編譯器碰到 new(12U, 3.4) CMyClass 時,第一步會調用此函數: CMyClass::operator new(size_t, unsigned int, double); 其中 p2 = 12U 而 p3 = 3.4。第二步才會調用 CMyClass::CMyClass() 預設建構子。 關於自訂 operator new,實際範例於下。 =========================================================================== // 語法參考:http://msdn.microsoft.com/en-us/library/t48aek43.aspx #include <iostream> #include <new> class CMy { public: int val; CMy(int i) : val(i) { printf("val = %d\n", val); } CMy() : val(5566) { printf("In Def ctor, val = %d\n", val); } // 自訂版的 placement operator new void *operator new(size_t sz, void *ad) { puts("In placement operator new of CMy!!!"); // 強制修改物件的建構地點,且改用別的建構子 CMy *newAddr = (CMy *) ad + 4; printf(" From %p To %p\n", ad, (void *) newAddr); return ::new(newAddr) CMy(7788); } }; int main() { void *mallocAddr = malloc(512); printf("mallocAddr = %p\n", mallocAddr); CMy *ptr = NULL; ptr = new(mallocAddr) CMy(); printf("In %p, CMy::Val = %d\n", (void *) ptr, ptr->val); ptr = (CMy *) mallocAddr; printf("In %p, CMy::val = %d\n", (void *) ptr, ptr->val); return 0; } /* *執行結果:(codepad.org) *mallocAddr = 0x804f438 *In placement operator new of CMy!!! * From 0x804f438 To 0x804f448 *val = 7788 *In Def ctor, val = 5566 *In 0x804f448, CMy::Val = 5566 *In 0x804f438, CMy::val = -1819044973 */ =========================================================================== 在 new / deleter / malloc / free 以外的世界: (1) std::allocator 來自 #include <memory> (2) ... . . . (n) ... 聽人提過好幾個方案,但是我只記得 allocator 一個。 在 C++ Primer 4/e 中文版裡,如此這般形容 allocator: 「新一代 C++ 程式員應該使用 allocator class 來配置記憶體。」 「它更安全也更靈活」 很唬人... 簡單的 allocator 使用範例: http://msdn.microsoft.com/zh-tw/library/723te7k3.aspx 我覺得一句話:「allocator 介於 malloc 與 new 的中間」 舉例來說 寫 new CMyClass[10]; 時,表示配置 10 個 CMyClass 物件的空間, 並且呼叫他們的 ctor,全部一起初始化。 寫 CMyClass *ptr = (CMyClass *) malloc(10 * sizeof(CMyClass)); 時 就是只有配置 10 個 CMyClass 物件所需的記憶體,無 ctor 介入, 也沒辦法手動呼叫 ctor。除非使用 placement new 來達成: CMyClass *pObjNO1 = new(ptr) CMyClass(); CMyClass *pObjNO2 = new(ptr + 1) CMyClass(); 俗話說漢賊不兩立,這 malloc 明明是 C 陣營的,如果淪落到要靠 C++ new 來收尾, 那也太沒骨氣...如此方案,不用也罷... 寫 std::allocator<CMyClass> alctr; alctr.allocate(10); 跟 malloc 一樣,只是配置 10 個該物件的空間,卻不必花時間去初始化。 當真正需要初始化時,還是可以透過 alctr.construct(...) 完成 Copy Ctor。 如同 C++ Primer 的例子一般,當某個 vector 物件執行 push_back() 要新增 項目時,如果容器內的空間不夠,只要用 allocator 去動態配置, 就不必跟 new 一樣強制呼叫每一筆的 ctor。 比如可以配置幾萬個空位出來,真正建構的只有一個,被 push_back() 的那個。 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 124.8.129.228
TaiwanXDman:這個只能推了..// 04/11 03:46
※ 編輯: purpose 來自: 124.8.133.108 (04/11 08:58)
hilorrk:推認真! 04/11 08:49
angleevil:<(__)> 04/11 09:23
EdisonX:推推推!!! <小聲:配置失敗的處理也是一個 section.> 04/11 09:39
calqlus:早安 04/11 09:49
VictorTom:大推....Orz 04/11 10:17
cory8249:太強了... 04/11 10:42
kingweirong:大推!! 04/11 11:31
hichcock:不能不給推呀~~ 04/11 11:50
shadow0326:推 04/11 12:00
saxontai:這篇該 m 04/11 12:55
xatier:推! 04/11 14:04
QQ29:請問一下placement new 有什麼必要的用途嗎 04/11 16:22
QQ29:看起來 可以用來搭配 一個陣列 搭配 每個element 可以for一圈 04/11 16:23
QQ29:或 一個一個指定 要 呼叫哪一個 非預設 建構子??除此之外呢 04/11 16:24
QQ29:http://ideone.com/SaJAl cleanup的動作也是要我們手動嚕? 04/11 16:36
purpose:不需要在指定位置,呼叫指定建構子時,他就沒有用途 04/11 16:42
purpose:有 new 就要 delete 04/11 16:42
QQ29:但我程式把release那段都拔掉 改成 delete buffer; 04/11 17:23
QQ29:他指呼叫一次解構~ 改成delete []buffer; run time error 04/11 17:24
QQ29:該用for一圈 每個delete buffer[i]??嗎 04/11 17:24
QQ29:delete &buffer[i]; ~ 還是run time error...該怎麼寫呢 04/11 17:25
QQ29:#1BdX0yw4 這篇推文下了一個結論 要手動呼叫 是錯的嗎? 04/11 17:27
Chikei:main usages from wikipedia: http://goo.gl/EWsnH 04/11 18:18
一言難盡,下文補充 delete 的資訊 新世紀:「有 new 就要有 delete」 小時候大人都說,有 new 就要有 delete,這是遊戲規則,一定要遵守。 其目的為何?簡單來說是為了避免 ctor 中有取得資源的動作,所以必須呼叫 dtor, 還有釋放當初替此物件 HeapCreate 得來的記憶體空間,避免 Memory Leak。 所以與其說「必須 delete」,不如說「必須達到 dtor 與 free 的目的」 回到 QQ29 的問題,只要你有達到 delete 的目的,做任何等價的事都是可以的。 所以如果你寫的程式馬上就結束,那什麼事都不做,同樣是可以的。 看追求的目標有沒有達到就是了,這場比賽,其實沒有遊戲規則。 解鈴還須繫鈴人:「你寫的 new 應當用你寫的 delete」 以下歸納如何處理 delete 的議題 (意思是,如何完成 delete 的目標)。 上文我貼的程式碼內,有一個自訂的 CMy::operator new,可以發現他的行為是 無理取鬧的,雖然呼叫方指定的位址是 p,真正被選擇用來建構物件的位址 卻在 p 位址的後方。 在這樣的情況之下,傻傻的使用 built-in delete 的話,很容易搞錯對象。 所以 custom new 請搭配 custom delete。 有唐山公無唐山嬤:「有 placement new 沒有 placemement delete」 成套的東西都是比較好的。 內建的 int *p = new int; 用對應的內建版 delete p; 處理,很安全。 內建的 int *pa = new int[3]; 同樣用 delete []pa; 處理,很安全。 但是 C++ 雖然有內建的 placement new,卻沒有內建版 placement delete。 是不太合邏輯,但不是我能決定的。 回到最初使用 delete 的目的,還是能想出對應的方法。 第一:呼叫 dtor 第二:釋放 Heap 空間 當你於 Rumtime 動態配置記憶體,不管是使用 malloc 還是其他手法,總之 你會得到一段連續的位址,假設是 0x1000~0x2000,你就用個 void *ptr; 記錄。 當你想要在 ptr 裡面的某處,去建構某物件時,你會發覺除了 placement new 以外,沒有其他方法,所以你就用了。 用完後要刪除物件,總是得先 dtor,所以只能用 static_cast<CMy *>(pobj)->~CMy(); 也就是推文中 QQ29 說的手動呼叫。 至於 ptr 的空間,看當初用什麼方法配置,就要用相應的方法去釋放。 而且釋放之前要想清楚,如果他是一塊 0x1000 大小的 Heap Block,你就只能 一次性釋放全部,不可能只挑其中某處 4 位元組去釋放,至少在 Windows 的 Heap Manager 機制是這樣運行。 神奇的 delete[] 與平凡的 free() Class CMy { private: double realValue; }; CMy *pObjs = new CMy[12]; delete[] pObjs; 這裡的 CMy 物件有 8 Bytes,所以我們知道 built-in operator new 會用 malloc 去配置 8*12 = 96 位元組的 Heap 空間。 當 delete 該位址時,就是使用 free(位址) 去釋放 這個 96 Bytes 的 Heap Block。 到目前為止,理論上應該嗆一句 new / delete 也沒什麼,跟 malloc / free 都 嘛差不多。但神奇的地方在 delete [] 位址時,其實會呼叫 12 次 dtor, 分別呼叫那 12 物件的解構子,這是怎麼做到的? 以 VC2010 舉例,其他我沒測過。 CMy *pObjs = new CMy[12]; 假設回傳的位址是 0x50008000 在執行這行的時候,就開始動手腳了,實際上 (int *) pObjs - 1 這個地方 編譯器會偷存該陣列的項目數 12。 所以 delete 時,一來有 0x50008000 這筆資訊,又有 sizeof(CMy) = 8, 加上 12 個物件這項資訊,所以可以輕易得到 0x50008000 == pObjs[0] 0x50008008 == pObjs[1] 0x50008010 == pObjs[2] . . . 0x500080B0 == pObjs[11] 而 destructor 函數的位址,編譯器也早就能取得,他直接無中生有的呼叫 12 次 dtor 即可,當然每次呼叫傳入的 this 指標都會照上面那樣改變, 任務就能完成。 所以如果某位址 addr = 0x1004 的話,使用 delete addr,在進入 內建 operator delete 時,會 free(0x1004); 但使用 delete []addr ,在進入 built-in operator delete 時, 會改成 free(0x1000),因為他會認為其中有藏陣列項目數,會認為 真正此 Heap Block 的起始位址是在 0x1000 才正確。 如果 free() 接收到錯誤的 heap block 起始位址,會導致釋放錯誤。 ※ 編輯: purpose 來自: 124.8.131.238 (04/11 19:47)
s3748679:好拼... 不過~ 支持 04/11 21:34
QQ29:好深奧,所以看樣子推文的code 還是可以接受的嗎 04/11 21:47
QQ29:手動呼叫dstr總覺得還是怪怪的 04/11 21:49
QQ29:若多型,也不確定該怎呼叫derived的dstr..不確定型態 04/11 21:50
你應該要嘗試自已驗證對不對,不然問不完。 有沒有釋放記憶體,這你搜尋 memory leak 就一堆教學。 有沒有 dtro,這不是自己印個字串或下個中斷點就能判斷。 多型的狀況,這其實隨便把關鍵字打進 google,第一頁就有答案: http://msdn.microsoft.com/en-us/library/35xa3368.aspx #include <iostream> class CAnimal { public: virtual ~CAnimal() { puts("~CAnimal"); } }; class CDog : public CAnimal { public: ~CDog() { puts("~CDog"); } }; class CCerberus : public CDog { public: ~CCerberus() { puts("~CCerberus"); } }; int main() { void *p = malloc(256); CCerberus *lucky = new(p) CCerberus; CDog *dog = lucky; dog->~CDog(); return 0; } 輸出 ~CCerberus ~CDog ~CAnimal ※ 編輯: purpose 來自: 124.8.131.238 (04/11 22:32)
EdisonX:建議 QQ29 真想了解的話,可「輕微」研究 OOC 是怎麼撰之, 04/11 22:31
EdisonX:這些事情 C++ 都已自動交辦給 compiler 處理 04/11 22:33
lsc36:推超專業文 04/11 22:35
WJ1224:推~~~ 04/11 23:11
im2a27:有神快拜 04/11 23:21
jackylu63:三行的答案, 被你寫成18頁的文章... 04/12 00:55
james732:拜託板主,這篇一定要M 04/12 00:58
FAITHY:推~用心又專業~ 04/12 01:04
angleevil:厄..這篇其實已經夠完整了,特別針對QQ29的問題. 04/12 11:13
angleevil:你用哪種型態的new,就一定要有相對應delete 04/12 11:14
NeverCareU:推! 04/14 15:53
dragonrose:看不懂 先拜好了XD 03/27 23:06