作者purpose (purpose)
看板C_and_CPP
標題Re: [問題] c和c++配置記憶體的差別
時間Wed Apr 11 02:19:53 2012
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
→ 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
一言難盡,下文補充 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