operator newnew Type 兩步驟:① 呼叫 operator new(sizeof(Type)) 配置記憶體 ② 呼叫建構函數。將 operator new/delete 宣告為 private,外部無法呼叫 → 無法在堆上建構。
class NoHeapObject {
private:
// 禁止外部 new/delete
static void* operator new(std::size_t size) {
return ::operator new(size); // 內部仍可用全域 ::
}
static void operator delete(void* ptr) noexcept {
::operator delete(ptr);
}
// 禁止陣列 new/delete
static void* operator new[](std::size_t size) = delete;
static void operator delete[](void* ptr) noexcept = delete;
Resource* resource_; // 假設資源
public:
NoHeapObject() : resource_(new Resource()) {} // 內部 new 合法 (存取 private)
~NoHeapObject() { delete resource_; }
// 若需堆上建構,提供受控工廠方法
static std::unique_ptr create() {
return std::unique_ptr(new NoHeapObject());
}
};
// 使用
NoHeapObject obj; // ✅ 堆疊建構合法
// NoHeapObject* p = new NoHeapObject; // ❌ 編譯錯誤:operator new private
auto p = NoHeapObject::create(); // ✅ 受控堆上建構
原文展示的「暴力破解」本質:手動配置記憶體 → Placement New 建構 → 強制轉換存取。
// 破解步驟 (教學展示,生產嚴禁)
void hack() {
// 1. 配置原始記憶體 (繞過 operator new)
char* raw = new char[sizeof(NoHeapObject)]; // 或 malloc
// 2. Placement New 在指定記憶體建構 (需存取 private 建構函數)
// 若建構函數也是 private,需友元或暴力指標運算
NoHeapObject* obj = new (raw) NoHeapObject(); // 若建構函數 public
// 3. 使用物件
obj->doSomething();
// 4. 手動解構 + 釋放記憶體
obj->~NoHeapObject(); // 顯式呼叫解構函數
delete[] raw; // 釋放原始記憶體
}
關鍵點:Placement New new (ptr) Type() 僅呼叫建構函數,**不呼叫 operator new**,故繞過 private 限制。但若建構函數同為 private,仍需友元或更深層破解。
class NoHeapObject {
Resource* ptr_; // 假設位於 offset 0
int data_;
};
// 破解者假設 ptr_ 在 offset 0:
Resource** hacked_ptr = reinterpret_cast(obj); // 極度危險
// 不同編譯器/最佳化/繼承/虛擬函數/對齊 皆可能改變佈局 → UB
這正是 C++ 物件模型不保證佈局的原因,**嚴禁**在生產代碼依賴記憶體佈局假設。
| 需求 | 推薦方案 | 優勢 |
|---|---|---|
| 強制堆疊建構 | 私有 operator new + 工廠回傳 unique_ptr | 型別安全、RAII、編譯期強制 |
| 固定記憶體池分配 | 自定義分配器 + std::pmr::polymorphic_allocator (C++17) | 標準化、可攜、效能可控 |
| 嵌入式無堆環境 | 靜態儲存區 / 堆疊 / std::array / 記憶體池 | 確定性、無動態分配 |
| 單例/全域唯一 | Meyers Singleton (函數區域靜態) | 線程安全、自動釋放、無 new |