Re: [問題] c和c++配置記憶體的差別
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
推
04/11 03:46, , 1F
04/11 03:46, 1F
※ 編輯: purpose 來自: 124.8.133.108 (04/11 08:58)
推
04/11 08:49, , 2F
04/11 08:49, 2F
推
04/11 09:23, , 3F
04/11 09:23, 3F
推
04/11 09:39, , 4F
04/11 09:39, 4F
推
04/11 09:49, , 5F
04/11 09:49, 5F
推
04/11 10:17, , 6F
04/11 10:17, 6F
推
04/11 10:42, , 7F
04/11 10:42, 7F
推
04/11 11:31, , 8F
04/11 11:31, 8F
推
04/11 11:50, , 9F
04/11 11:50, 9F
推
04/11 12:00, , 10F
04/11 12:00, 10F
推
04/11 12:55, , 11F
04/11 12:55, 11F
推
04/11 14:04, , 12F
04/11 14:04, 12F
推
04/11 16:22, , 13F
04/11 16:22, 13F
→
04/11 16:23, , 14F
04/11 16:23, 14F
→
04/11 16:24, , 15F
04/11 16:24, 15F
推
04/11 16:36, , 16F
04/11 16:36, 16F
→
04/11 16:42, , 17F
04/11 16:42, 17F
→
04/11 16:42, , 18F
04/11 16:42, 18F
推
04/11 17:23, , 19F
04/11 17:23, 19F
→
04/11 17:24, , 20F
04/11 17:24, 20F
→
04/11 17:24, , 21F
04/11 17:24, 21F
→
04/11 17:25, , 22F
04/11 17:25, 22F
→
04/11 17:27, , 23F
04/11 17:27, 23F
推
04/11 18:18, , 24F
04/11 18:18, 24F
一言難盡,下文補充 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)
推
04/11 21:34, , 25F
04/11 21:34, 25F
推
04/11 21:47, , 26F
04/11 21:47, 26F
→
04/11 21:49, , 27F
04/11 21:49, 27F
→
04/11 21:50, , 28F
04/11 21:50, 28F
你應該要嘗試自已驗證對不對,不然問不完。
有沒有釋放記憶體,這你搜尋 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)
推
04/11 22:31, , 29F
04/11 22:31, 29F
→
04/11 22:33, , 30F
04/11 22:33, 30F
推
04/11 22:35, , 31F
04/11 22:35, 31F
推
04/11 23:11, , 32F
04/11 23:11, 32F
→
04/11 23:21, , 33F
04/11 23:21, 33F
推
04/12 00:55, , 34F
04/12 00:55, 34F
推
04/12 00:58, , 35F
04/12 00:58, 35F
推
04/12 01:04, , 36F
04/12 01:04, 36F
→
04/12 11:13, , 37F
04/12 11:13, 37F
→
04/12 11:14, , 38F
04/12 11:14, 38F
推
04/14 15:53, , 39F
04/14 15:53, 39F
推
03/27 23:06, , 40F
03/27 23:06, 40F
討論串 (同標題文章)
完整討論串 (本文為第 2 之 2 篇):