為什麼需要堆記憶體?
- 陣列大小必須編譯期確定(靜態/棧上分配),無法適應運行期才知曉的資料量。
- 大型物件放棧易導致棧溢出(預設 1~8 MB)。
- 物件生命週期需超出當前作用域(如工廠函數回傳、跨線程共享)。
C 風格:malloc / free
基本用法
#include <cstdlib> // 或 <malloc.h>
int n; cin >> n;
int *arr = (int*)malloc(n * sizeof(int)); // 強制轉型,malloc 返回 void*
if (!arr) { /* 配失敗處理 */ }
for (int i=0; i<n; ++i) arr[i] = i;
// 使用完畢
free(arr); // 必須成對調用,否則記憶體洩漏
關鍵特性
| 特性 | 說明 |
| 不呼叫建構子/解構子 | 僅分配原始位元組,適用於 POD 型別;類別物件需手動 placement new |
| 可重新調整大小 | realloc(ptr, new_size) 保留資料擴縮記憶體 |
| 無型別安全 | 返回 void*,需手動轉型,易出錯 |
失敗返回 NULL | 不拋異常,必須檢查返回值 |
C++ 風格:new / delete
基本用法
int n; cin >> n;
int *arr = new int[n]; // 分配陣列,自動呼叫預設建構子
// 使用...
delete[] arr; // 陣列必須用 delete[],單一物件用 delete
核心優勢
- 型別安全:返回精確型別指標,無需轉型。
- 自動建構/解構:逐元素呼叫建構子與解構子,支援非 POD 類別。
- 失敗拋異常:預設拋
std::bad_alloc,可用 new (std::nothrow) 改為返回 nullptr。
- 可重載/自定義:類別可重載
operator new/delete,實現記憶體池、診斷追蹤等。
深度對比:為什麼 C++ 專案禁用 malloc/free?
class Widget {
public:
Widget() { cout << "ctor\n"; }
~Widget() { cout << "dtor\n"; }
};
void c_style() {
Widget *w = (Widget*)malloc(sizeof(Widget)); // 僅分配記憶體,ctor 未呼叫!
free(w); // dtor 同樣未呼叫,資源洩漏隱患
}
void cpp_style() {
Widget *w = new Widget(); // 分配 + ctor
delete w; // dtor + 釋放
}
進階主題:記憶體對齊與 aligned_alloc / operator new
- C++17
std::aligned_alloc、C11 aligned_alloc 支援指定對齊邊界(如 SIMD、GPU DMA 緩衝區需 64/128 bytes 對齊)。
- C++ 可重載
static void* operator new(size_t, std::align_val_t) 實現類別級對齊控制。
現代 C++ 最佳實踐:智慧指標與容器
| 場景 | 推薦做法 | 避免 |
| 單一擁有權物件 | std::unique_ptr<T> / std::make_unique | 裸 new/delete |
| 共享擁有權 | std::shared_ptr<T> / std::make_shared | 手動參考計數 |
| 動態陣列/緩衝區 | std::vector<T> / std::string | new T[n] / malloc |
| 固定大小陣列 | std::array<T, N> (棧上) / std::unique_ptr<T[]> | VLA / malloc |
常見錯誤與除錯技巧
- 配對錯誤:
new[] 對 delete、malloc 對 delete —— 嚴重未定義行為。
- 重複釋放:同一指標
free 兩次 → 堆損壞;用智慧指標自動規避。
- 釋放後使用:
delete p; p->foo(); —— 用 AddressSanitizer (ASan) 編譯選項 -fsanitize=address 快速定位。
- 記憶體洩漏:
Valgrind / Visual Studio Diagnostic Tools / Dr. Memory 定期檢測。
總結
malloc/free 是 C 遺產,僅適合 POD 原始記憶體操作;C++ 專案**強烈建議**全面改用 new/delete 或更高層級封裝。
new/delete 提供型別安全、建構解構自動化、異常安全、可擴展性。
- 現代 C++ (C++11 以降) 應以
std::unique_ptr、std::vector、std::string 為主力,**極少**直接接觸裸指標與手動釋放。
- 理解底層機制是為了讀懂舊代碼、編寫高效能分配器、診斷疑難雜症,而非日常開發手段。
- 上一篇文章: C/C++ 字串常量比較陷阱與正確拷貝姿勢
- 下一篇文章: C++ 函數多載完全指南:同名不同參數的藝術
Copyright ©
程式碼之家 All Rights Reserved