1 語義層級不同:語言機制 vs. 庫函數
new / new[] (C++ 關鍵字) | malloc / calloc / realloc (C 運行時函數) | |
---|---|---|
本質 | 語言級運算符;可被重載 | 庫函數;無法重載 |
作用 | 分配內存 并調用構造函數 | 僅分配原始字節塊,不做初始化,也不調用構造函數 |
返回類型 | 指向 請求類型 的指針;無需強制類型轉換 | void* ;C++ 中需顯式轉換 |
失敗處理 | 默認拋出 std::bad_alloc ;nothrow 版本返回 nullptr | 返回 nullptr ,并設置 errno |
釋放方式 | delete / delete[] :先調用析構函數,再歸還內存 | free() :直接釋放字節塊 |
2 對象生命周期:構造 / 析構自動 VS 手動
// new:安全地構造對象
std::string* ps = new std::string("hi"); // 調用了 std::string 的構造函數
delete ps; // 調用析構函數,再歸還內存// malloc:只得到裸內存,需要手動“放置構造”(placement new)
void* raw = std::malloc(sizeof(std::string));
std::string* ps2 = new (raw) std::string("hi"); // 構造
ps2->~std::string(); // 手動析構
std::free(raw);
若忘記任何一步就會造成 未定義行為 或 內存泄漏。
3 類型與對齊保障
-
new
按目標類型所需的 最嚴對齊 分配;
自 C++17 起,malloc
也保證返回能滿足 max-align(通常 ≥16 byte) 的地址,但老代碼仍可能在自定義對齊要求(例如 SIMD 類型)下依賴new
。 -
new[]
還需在實現內部保存元素個數,以便delete[]
正確調用每個元素的析構函數——這部分管理開銷是malloc
不具備的。
4 可定制性
-
重載
operator new
/operator delete
全局 或 類內 均可,用于內存池、調試填充、對齊擴展等。
例:void* MyClass::operator new(std::size_t sz) { return MyPool::allocate(sz); } void MyClass::operator delete(void* p) { MyPool::deallocate(p); }
-
malloc
不能按對象粒度定制,只能整體替換底層分配器(如 jemalloc、tcmalloc)或在使用層面改寫為mymalloc()
。
5 異常安全與 RAII
new
的異常語義與 C++ 的 RAII 配合天然一致:對象構造失敗時拋異常,上層 catch 后無泄漏。
使用 malloc
時必須手動檢查返回值并在出錯路徑中 free()
已成功分配的資源。
6 何時選用哪一個?
場景 | 建議 |
---|---|
絕大多數 C++ 代碼,直接需要對象 | 使用 new /delete ——保證構造/析構正確發生 |
需要與 C 庫接口共享內存(libc , POSIX API, MPI 等)或操作純字節緩沖區 | 使用 malloc /free |
自己管理對象池、序列化緩沖,不希望逐對象開銷 | malloc +placement new 或者 重載 operator new |
性能敏感的大批量小對象 | 重載 operator new 或專業分配器(tcmalloc, jemalloc, std::pmr ) |
切勿交叉釋放:
malloc
→delete
?
new
→free
?
違者均是未定義行為。
7 常見誤解澄清
-
效率:
new
不必然更慢——主開銷往往在底層分配算法,而不是是否調用構造函數;自定義operator new
可以比裸malloc
更快。 -
初始化為 0:只有
new int()
或new int[10]()
才會零初始化;new int
保留未定義值,與malloc
返回值同樣“臟”。 -
內存來源:在大多數實現里
new
仍然最終調用某種malloc
,但附帶額外的語言級步驟;標準層面它們依舊完全不同的抽象。
一句話總結
new
= 分配內存 + 構造對象 + 類型安全 + 異常語義;
malloc
= 只要一塊原始字節,剩下的你自己負責。
掌握兩者差異,才能在系統編程與高性能場景中游刃有余。
是的,你理解得基本正確,我來系統梳理一下這件事:
? new
會自動調用構造函數(創建)和析構函數(銷毀)
MyClass* p = new MyClass(123); // 自動調用構造函數 MyClass(123)
...
delete p; // 自動調用析構函數 ~MyClass()
這個過程:
-
分配內存(內部可能調用
malloc
或其他分配器) -
調用構造函數,初始化對象
-
delete
會反過來先調用析構函數,再釋放內存
所以用
new
分配的對象,不需要你手動構造和析構,只要new
/delete
配對即可。
? malloc
只分配“原始內存”,不會自動構造或析構
void* mem = malloc(sizeof(MyClass)); // 沒有調用構造函數
MyClass* p = static_cast<MyClass*>(mem);
這時,p
指向一塊“未初始化的內存”,你不能直接使用它:
?你必須這樣手動構造:
new (p) MyClass(123); // placement new:在已有內存上構造對象
?用完后手動析構 + 釋放內存:
p->~MyClass(); // 顯式調用析構函數
free(p); // 再釋放原始內存
📌 總結一下區別:
操作 | new / delete | malloc / free |
---|---|---|
內存分配 | 自動完成 | 手動 malloc(sizeof(T)) |
構造函數調用 | 自動(帶參數或默認) | ? 不會;需手動 placement new |
析構函數調用 | 自動 | ? 不會;需手動 對象->~類名() |
安全性 / 易錯性 | 高,配合 RAII 可防泄漏 | 容易出錯,忘構造或析構都會導致 UB 或泄漏 |
? 示例對比(完整代碼)
使用 new
(推薦方式)
MyClass* p = new MyClass(10);
// ... 使用 p
delete p;
使用 malloc
(必須小心)
void* mem = malloc(sizeof(MyClass));
MyClass* p = new (mem) MyClass(10); // placement new
// ... 使用 p
p->~MyClass(); // 手動析構
free(mem); // 手動釋放內存
🔥 總結:用 malloc
管理對象時,你每次都必須手動調用構造和析構函數;
但用 new
時,這些操作都自動完成,更安全、更方便,推薦優先使用。