一、入門
1、說說new與malloc的基本用途
int* p1 = (int*)malloc(sizeof(int)); // C風格
int* p2 = new int(10); // C++風格,初始化為10
new
?是 C++ 中的運算符,用于在堆上動態分配內存并調用對象的構造函數,會自動計算所需內存大小。
#include <iostream>
int main() {int* ptr = new int(5);std::cout << *ptr << std::endl;delete ptr;return 0;
}
?malloc
?是 C 語言中的標準庫函數,用于在堆上分配指定大小的內存塊,不會調用對象的構造函數,返回的是?void*
?類型的指針,需要手動進行類型轉換。
int main() {int* ptr = (int*)malloc(sizeof(int));*ptr = 5;std::cout << *ptr << std::endl;free(ptr);return 0;
}
內存分配失敗處理:malloc
返回NULL
;new
拋出std::bad_alloc
異常?
2、delete
?和?free
?分別用于什么場景?
delete
?是 C++ 中的運算符,用于釋放由?new
?分配的內存,并調用對象的析構函數。
#include <iostream>
class MyClass {
public:~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* obj = new MyClass();delete obj;return 0;
}
free
?是 C 語言中的標準庫函數,用于釋放由?malloc
、calloc
?或?realloc
?分配的內存,不會調用對象的析構函數。?
#include <iostream>
#include <cstdlib>
int main() {int* ptr = (int*)malloc(sizeof(int));free(ptr);return 0;
}
3、new與malloc的關聯
new
通過調用operator new
分配內存,而默認的operator new
內部使用malloc
void* operator new(size_t size) { void* p = malloc(size); if (!p) throw std::bad_alloc(); return p;
}
4、delete NULL或nullptr會發生什么?
?操作? | ?空指針(NULL /nullptr )?? | ?非空指針? |
---|---|---|
delete ?/?delete[] | 安全(無操作) | 需確保指針有效且未被重復釋放 |
free | 安全(無操作) | 需確保內存由?malloc ?分配 |
?底層邏輯?:編譯器會檢查指針是否為空,若為空則直接跳過析構和內存釋放步驟?
最佳實踐?:釋放后立即置空指針
delete、free并不會把指針置空。
int* p = new int(10);
delete p; // 第一次釋放
delete p; // 危險!重復釋放非空指針
安全性:避免程序員在調用?delete
?或?free
?前必須顯式檢查指針是否為空(避免冗余檢查)。?
二、進階
1、new[]
?和?delete[]
?的作用是什么?
new[]
?用于在堆上動態分配數組內存,并對數組中的每個元素調用構造函數。delete[]
?用于釋放由?new[]
?分配的數組內存,并對數組中的每個元素調用析構函數
include <iostream>
class MyClass {
public:MyClass() {std::cout << "Constructor called" << std::endl;}~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* arr = new MyClass[3];delete[] arr;return 0;
}
C++ 編譯器在解析代碼時會忽略?
delete
?和?[]
?之間的所有空白符?(包括空格、換行符、制表符等),推薦?delete[]
?的緊湊寫法delete[]arr; delete []arr; delete [] arr; delete[] arr;
2、對于內置數據類型,使用delete、delete[]效果是一樣的。這句話對嗎?為什么?
內置類型無析構函數?:C++ 的內置數據類型(如?int
、long
、指針等)沒有析構函數。delete
?和?delete[]
?的核心差異在于是否調用析構函數,而內置類型無需析構,但內存釋放的完整性仍取決于運行時環境。某些編譯器(如 MSVC)可能通過內存池機制自動回收整個數組內存,但這屬于未定義行為,不可依賴。
int* p1 = new int(10);
delete p1; // 正確釋放單個對象
int* p2 = new int[10];
delete[] p2; // 正確釋放數組
delete p2; // 未定義行為
分配時的元數據記錄?:無論是?new
?還是?new[]
,內存分配時系統會記錄分配的內存大小和對象數量(存儲在?_CrtMemBlockHeader
?等結構中)。釋放時,delete
?和?delete[]
?均能通過指針獲取這些信息,從而正確釋放連續內存塊?。
int* p = new int[1000];
delete p; // 看似正常,但 Valgrind 報告 3996 字節泄漏(1000 * 4 - 4)
+-------------------+
| 數組長度(1000) | ← 元信息(通常占用 4/8 字節)
+-------------------+
| 元素0(int) | ← 用戶可見的指針 `p` 指向此處
+-------------------+
| 元素1(int) |
+-------------------+
| ...(共 1000 個) |
+-------------------+
3、使用?new
?分配內存,用?free
?釋放會怎么樣??
如果使用?new
?分配內存,卻用?free
?釋放,對象的析構函數不會被調用,可能會導致資源泄漏,例如對象中包含動態分配的資源(如文件句柄、網絡連接等)無法正確釋放。
#include <iostream>
class MyClass {
public:~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* obj = new MyClass();free(obj); // 析構函數不會被調用return 0;
}
4、?使用?malloc
?分配內存,用?delete
?釋放會怎么樣?
delete
?會嘗試調用對象的析構函數。?malloc
?分配的內存沒有經過構造函數初始化,調用析構函數可能會導致未定義行為。
#include <iostream>
#include <cstdlib>class ResourceHolder {
public:ResourceHolder() {std::cout << "ResourceHolder: Acquiring resource..." << std::endl;// 模擬資源獲取,例如打開文件、分配內存等resource = new int[100];}~ResourceHolder() {std::cout << "ResourceHolder: Releasing resource..." << std::endl;// 模擬資源釋放,例如關閉文件、釋放內存等delete[] resource;}private:int* resource;
};int main() {// 使用 malloc 分配內存ResourceHolder* holder = (ResourceHolder*)malloc(sizeof(ResourceHolder));if (holder == nullptr) {std::cerr << "Memory allocation failed!" << std::endl;return 1;}// 嘗試使用 delete 釋放內存delete holder;return 0;
}
- 運用?
malloc
?為?ResourceHolder
?對象分配內存。malloc
?只是單純地分配指定大小的內存塊,不會調用對象的構造函數,所以?resource
?指針不會被正確初始化。 - 嘗試使用?
delete
?釋放內存。delete
?會調用對象的析構函數,但是由于?resource
?指針未被正確初始化,在析構函數中調用?delete[] resource
?就會引發未定義行為,可能會導致程序崩潰或者出現其他不可預測的問題。- 當?
delete[] resource
?嘗試釋放一個未正確初始化的指針時,可能會訪問非法內存地址,從而致使程序崩潰。
- 當?
5、malloc
?+?delete混用問題
注:new
和delete
必須成對使用
- 內存生命周期管理:
new
與delete
通過構造函數/析構函數保證對象完整生命周期 - ?混用風險:未調用析構函數(若對象有資源需釋放)
class MyClass {int* data; // 未初始化
public:MyClass() { data = new int[100]; } // 構造函數未執行!~MyClass() { delete[] data; } // 析構函數嘗試釋放野指針
};MyClass* p = (MyClass*)malloc(sizeof(MyClass));
delete p; // 析構函數調用delete[] data,但data未初始化 → 崩潰
delete
確實會調用析構函數,但這一行為是否能正確執行,取決于對象是否被正確構造。通過malloc
分配內存時,MyClass
的構造函數未被調用,但delete p
卻嘗試調用析構函數。若析構函數中存在對未初始化成員的操作(如delete data
),會導致未定義行為?(如訪問野指針,引發崩潰)
內置類型(如
int
)?:
無構造函數和析構函數,因此malloc
+delete
可能不會崩潰(因為沒有析構操作),但仍是未定義行為int* p = (int*)malloc(sizeof(int)); delete p; // 可能不崩潰,但不符合規范
6、new
與free混用
未調用析構函數,且可能因內存布局差異導致崩潰(如new[]的頭部信息未處理)
當通過new[]
分配數組時,內存布局可能包含頭部信息?(記錄數組長度),例如:
MyClass* arr = new MyClass[5];
// 內存布局:[長度=5][對象1][對象2]...[對象5]
delete[]
會根據頭部信息調用5次析構函數,再釋放完整內存塊。若用free
釋放,?頭部信息未被處理。free
?的輸入是?arr
(指向第一個對象),但?new[]
?分配的實際內存塊起始地址是?arr - sizeof(頭部)。
正確行為:若用戶調用?delete[] arr
,會從頭部地址釋放完整內存塊(包括頭部和所有對象)。?錯誤行為:若調用?free(arr)
,free
?僅嘗試釋放從?arr
?開始的地址,而實際分配的內存塊起始位置未被正確識別,導致部分內存未被釋放?(內存泄漏)或堆結構破壞?(可能崩潰)
free(arr)
?無法識別?new[]
?的內存布局,會釋放不完整的地址范圍,導致內存泄漏(頭部和部分對象未被釋放),甚至因堆管理器元數據損壞而崩潰。
注:不是只釋放了[長度=5]
[對象1][對象2]...[對象5]
free
只能釋放通過malloc
/calloc
/realloc
分配的內存塊,其底層通過內存塊的頭部元數據?(如大小信息)來釋放整個內存塊。
new[]
分配的頭部信息可能格式與malloc
不同,導致free
無法正確解析,最終釋放的地址范圍是未定義的:
- ?可能釋放不完整:
free(arr)
可能僅釋放從對象0
地址開始的部分內存(如對象0
的存儲空間),而頭部和其他對象的內存未被釋放- ?可能破壞堆結構:錯誤釋放地址會導致堆管理器元數據損壞,引發后續內存操作崩潰
若編譯器未添加頭部信息,free(arr)
?可能釋放整個數組(因內存塊連續),但這是未定義行為,依賴具體實現。(內置類型數組)
- 析構函數未被調用 → 資源泄漏。
- 釋放的地址錯誤(如未回退到頭部起始位置)→ 內存布局破壞,可能崩潰
三、高階
1、如何重載?new
?和?delete
?運算符?
?應用場景:
- 內存池優化(減少碎片)
- 調試內存泄漏(記錄分配/釋放日志)
#include <iostream>
#include <cstdlib>
class MyClass {
public:static void* operator new(size_t size) {std::cout << "Custom new operator called" << std::endl;return std::malloc(size);}static void operator delete(void* ptr) {std::cout << "Custom delete operator called" << std::endl;std::free(ptr);}~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* obj = new MyClass();delete obj;return 0;
}
2、?如何捕獲new過程異常并處理
int main() {try {while (true) {int* ptr = new int[1000000];}} catch (const std::bad_alloc& e) {std::cout << "Memory allocation failed: " << e.what() << std::endl;}return 0;
}
3、?delete
和delete[]
有何區別?
delete
釋放單個對象;delete[]
釋放數組
delete
調用一次析構函數;delete[]
對數組中每個元素調用析構函數
對數組使用delete
會導致內存泄漏或崩潰。
4、new[]
如何知道要調用多少次析構函數?
頭部信息存儲:當分配自定義類型數組時,new[]
會在內存塊頭部額外存儲數組長度(如4/8字節)
delete[]
根據頭部信息確定析構次數,再釋放完整內存塊
MyClass* arr = new MyClass[5];
// 內存布局:[長度=5][對象1][對象2]...[對象5]
delete[] arr; // 讀取長度5,調用5次析構函數
內置類型數組:若數組元素是基本類型(如?int
),某些編譯器可能不添加頭部信息(因無需調用析構函數),直接分配連續內存?
?5、設計一個內存泄漏檢測工具,如何跟蹤new
/delete
的使用?
?重載全局operator new/delete
:記錄分配/釋放的地址和大小。
哈希表跟蹤:維護分配記錄,檢測未配對的new
和delete
std::map<void*, size_t> allocMap;
void* operator new(size_t size) { void* p = malloc(size); allocMap[p] = size; return p;
}
void operator delete(void* p) { if (allocMap.erase(p)) free(p); else logLeak(); // 檢測到未記錄釋放
}