當(dāng)delete遭遇“失憶”:內(nèi)存釋放謎團(tuán)全解析
在C++的編程世界里,內(nèi)存管理是開(kāi)發(fā)者們繞不開(kāi)的關(guān)鍵話題。當(dāng)我們使用delete關(guān)鍵字來(lái)釋放內(nèi)存時(shí),一個(gè)有趣的問(wèn)題出現(xiàn)了:delete在執(zhí)行釋放操作時(shí),它并不知道所操作的內(nèi)存大小,那它究竟是如何準(zhǔn)確無(wú)誤地完成內(nèi)存釋放任務(wù)的呢?這就好比我們要清理一個(gè)倉(cāng)庫(kù),但卻不知道倉(cāng)庫(kù)的大小,該從何下手呢?
對(duì)于C++開(kāi)發(fā)者來(lái)說(shuō),內(nèi)存管理的重要性不言而喻。正確地分配和釋放內(nèi)存,是保障程序穩(wěn)定運(yùn)行、避免內(nèi)存泄漏和提高性能的關(guān)鍵。如果把程序比作一座大廈,那么內(nèi)存就是大廈的基石,而內(nèi)存管理則是建造和維護(hù)這座大廈的關(guān)鍵技術(shù)。稍有不慎,就可能導(dǎo)致大廈搖搖欲墜,程序出現(xiàn)各種難以調(diào)試的問(wèn)題。所以,深入探究delete釋放內(nèi)存的機(jī)制,就顯得尤為重要。接下來(lái),就讓我們一起揭開(kāi)它神秘的面紗吧!
一、內(nèi)存管理基礎(chǔ)概念
1.1程序內(nèi)存區(qū)域劃分
在深入剖析delete關(guān)鍵字的內(nèi)存釋放機(jī)制之前,我們有必要先理清 C++ 程序運(yùn)行時(shí)的內(nèi)存區(qū)域劃分邏輯 —— 這是理解內(nèi)存管理的核心前提。通常情況下,一個(gè)正在執(zhí)行的 C++ 程序,其內(nèi)存空間會(huì)被系統(tǒng)性地劃分為以下幾個(gè)關(guān)鍵區(qū)域:
圖片
- 棧區(qū)(Stack):由編譯器自動(dòng)分配和釋放,主要存放函數(shù)的參數(shù)值、局部變量等。它的特點(diǎn)是內(nèi)存分配和釋放效率高,就像一個(gè)先進(jìn)后出的棧結(jié)構(gòu)。比如我們?cè)诤瘮?shù)內(nèi)部定義一個(gè)局部變量int num = 10;,這個(gè)num變量就存放在棧區(qū),當(dāng)函數(shù)執(zhí)行結(jié)束,這個(gè)變量所占用的棧內(nèi)存會(huì)被自動(dòng)釋放。棧區(qū)的大小在編譯時(shí)就已經(jīng)確定,并且相對(duì)較小,如果在棧區(qū)分配過(guò)大的內(nèi)存,可能會(huì)導(dǎo)致棧溢出錯(cuò)誤。
 - 堆區(qū)(Heap):一般由程序員手動(dòng)分配和釋放,用于存放動(dòng)態(tài)分配的對(duì)象和數(shù)據(jù)。與棧區(qū)不同,堆區(qū)的內(nèi)存分配和釋放比較靈活,但也容易出現(xiàn)內(nèi)存泄漏等問(wèn)題。例如我們使用new關(guān)鍵字來(lái)分配內(nèi)存,int* p = new int;,這里分配的內(nèi)存就在堆區(qū)。堆區(qū)的內(nèi)存大小在程序運(yùn)行時(shí)可以動(dòng)態(tài)調(diào)整,但由于其分配和釋放需要程序員手動(dòng)管理,所以如果不小心忘記釋放不再使用的內(nèi)存,就會(huì)導(dǎo)致內(nèi)存泄漏,使程序占用的內(nèi)存越來(lái)越多,最終影響系統(tǒng)性能。
 - 數(shù)據(jù)段(Data Segment):用于存放已初始化的全局變量和靜態(tài)變量。數(shù)據(jù)段又可以細(xì)分為已初始化數(shù)據(jù)段和未初始化數(shù)據(jù)段(BSS 段)。已初始化數(shù)據(jù)段存放那些在程序中已經(jīng)被賦予初始值的全局變量和靜態(tài)變量,比如int globalVar = 10;,這個(gè)globalVar就存放在已初始化數(shù)據(jù)段;而未初始化數(shù)據(jù)段則存放那些聲明了但未初始化的全局變量和靜態(tài)變量,像int uninitGlobalVar; 。數(shù)據(jù)段的內(nèi)存是在程序加載時(shí)由系統(tǒng)分配,程序結(jié)束時(shí)由系統(tǒng)釋放。
 - 代碼段(Code Segment):存放函數(shù)體的二進(jìn)制代碼,也就是程序執(zhí)行的指令。代碼段通常是只讀的,以防止程序在運(yùn)行過(guò)程中意外修改自身的代碼。在代碼段中,也可能包含一些只讀的常量,比如字符串常量"Hello, World!"。當(dāng)多個(gè)進(jìn)程運(yùn)行相同的程序時(shí),它們可以共享同一個(gè)代碼段,這樣可以節(jié)省內(nèi)存空間 。
 
在這些內(nèi)存區(qū)域中,堆內(nèi)存是我們使用delete操作的主要對(duì)象,由于它需要開(kāi)發(fā)者手動(dòng)管理,所以也最容易出現(xiàn)內(nèi)存釋放相關(guān)的問(wèn)題。了解了內(nèi)存區(qū)域的劃分,我們就可以更好地理解delete在內(nèi)存管理中的角色和作用。
1.2C++ 內(nèi)存管理方式
在 C++ 中,內(nèi)存管理主要有兩種方式:C 語(yǔ)言風(fēng)格的malloc/free和 C++ 特有的new/delete。
malloc和free是 C 語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供的函數(shù),用于動(dòng)態(tài)分配和釋放內(nèi)存。malloc函數(shù)用于從堆中分配指定大小的內(nèi)存塊,它返回一個(gè)指向分配內(nèi)存起始地址的指針,并且不會(huì)對(duì)分配的內(nèi)存進(jìn)行初始化。例如:
int* p = (int*)malloc(sizeof(int));
if (p != nullptr) {
    *p = 10; // 手動(dòng)賦值
}使用完內(nèi)存后,需要調(diào)用free函數(shù)來(lái)釋放內(nèi)存:
free(p);
p = nullptr; // 防止野指針如果忘記調(diào)用free釋放內(nèi)存,就會(huì)導(dǎo)致內(nèi)存泄漏。
而new和delete是 C++ 的運(yùn)算符,它們不僅負(fù)責(zé)內(nèi)存的分配和釋放,還會(huì)自動(dòng)調(diào)用對(duì)象的構(gòu)造函數(shù)和析構(gòu)函數(shù)。對(duì)于自定義類型,這一點(diǎn)尤為重要。比如我們有一個(gè)自定義類MyClass:
class MyClass {
public:
    MyClass() {
        // 構(gòu)造函數(shù),初始化資源
        std::cout << "MyClass constructor" << std::endl;
    }
    ~MyClass() {
        // 析構(gòu)函數(shù),釋放資源
        std::cout << "MyClass destructor" << std::endl;
    }
};使用new來(lái)創(chuàng)建對(duì)象:
MyClass* obj = new MyClass;這里new會(huì)先分配足夠的內(nèi)存,然后調(diào)用MyClass的構(gòu)造函數(shù)來(lái)初始化對(duì)象。當(dāng)我們不再需要這個(gè)對(duì)象時(shí),使用delete來(lái)釋放內(nèi)存:
delete obj;delete會(huì)先調(diào)用MyClass的析構(gòu)函數(shù)來(lái)釋放對(duì)象占用的資源,然后再釋放內(nèi)存。
對(duì)比malloc/free和new/delete,可以發(fā)現(xiàn)new/delete在處理自定義類型時(shí)更加安全和方便,因?yàn)樗茏詣?dòng)管理對(duì)象的生命周期。但需要注意的是,new和delete必須配對(duì)使用,如果使用new分配內(nèi)存,卻使用free釋放,或者反之,都會(huì)導(dǎo)致未定義行為,可能引發(fā)程序崩潰或其他難以調(diào)試的問(wèn)題。同時(shí),在使用new[]分配數(shù)組內(nèi)存時(shí),必須使用delete[]來(lái)釋放,以確保數(shù)組中每個(gè)元素的析構(gòu)函數(shù)都能被正確調(diào)用。
1.3堆(Heap)的分類與管理
堆,作為內(nèi)存管理中的重要組成部分,就像是一個(gè)巨大的物資儲(chǔ)備庫(kù),它為程序提供了動(dòng)態(tài)分配內(nèi)存的區(qū)域。在 Glibc 堆內(nèi)存管理中,堆主要分為兩類,即主 Arena 的堆和子 Arena 的堆,它們各自有著獨(dú)特的特點(diǎn)和管理方式。
lang=c
/* 該數(shù)據(jù)結(jié)構(gòu)只在子Arena中使用,用于記錄當(dāng)前堆信息。 */
typedef struct _heap_info
{
  mstate ar_ptr; /* Arena for this heap. */ // 指向該堆所在的Arena
  struct _heap_info *prev; /* Previous heap. */ //由于一個(gè)子Arena管理多個(gè)堆,因此
  size_t size;   /* Current size in bytes. */ //當(dāng)前堆分配給用戶使用的大小,剩余部分為預(yù)留區(qū)域
  size_t mprotect_size; /* Size in bytes that has been mprotected
                           PROT_READ|PROT_WRITE.  */ //從代碼來(lái)看,和size并無(wú)區(qū)別(本人意見(jiàn))
  /* Make sure the following data is properly aligned, particularly
     that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
     MALLOC_ALIGNMENT. */
  char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; //用于對(duì)齊
} heap_info;主 Arena 的堆是通過(guò)brk系統(tǒng)調(diào)用從操作系統(tǒng)獲取內(nèi)存,它只有一個(gè),就像一個(gè)大型的中央倉(cāng)庫(kù),位于進(jìn)程地址空間的特定區(qū)域,從低地址向高地址增長(zhǎng)。主 Arena 的堆在初始化時(shí),其大小通常是一個(gè)較小的值,但隨著程序運(yùn)行過(guò)程中對(duì)內(nèi)存的不斷需求,它可以通過(guò)brk系統(tǒng)調(diào)用動(dòng)態(tài)擴(kuò)展。例如,當(dāng)一個(gè)程序需要分配更多內(nèi)存時(shí),brk系統(tǒng)調(diào)用會(huì)將堆的末尾地址移動(dòng)到所需內(nèi)存塊的末尾地址,從而為程序提供新的內(nèi)存空間。這就好比中央倉(cāng)庫(kù)在物資不足時(shí),可以通過(guò)擴(kuò)建來(lái)增加存儲(chǔ)容量。主 Arena 的堆在內(nèi)存管理中承擔(dān)著重要的角色,它是許多小型內(nèi)存分配的主要來(lái)源,由于其內(nèi)存分配和釋放的操作相對(duì)頻繁,因此需要高效的管理機(jī)制來(lái)確保內(nèi)存的合理使用。
子 Arena 的堆則是通過(guò)mmap系統(tǒng)調(diào)用創(chuàng)建的,與主 Arena 的堆不同,子 Arena 的堆可以有多個(gè),并且這些堆之間通過(guò)鏈表進(jìn)行連接。這就像是多個(gè)分散的小型倉(cāng)庫(kù),每個(gè)倉(cāng)庫(kù)都有自己獨(dú)立的管理方式。子 Arena 的堆通常用于滿足一些特殊的內(nèi)存分配需求,或者在多線程環(huán)境下,為不同的線程提供獨(dú)立的內(nèi)存分配空間,以減少線程之間的內(nèi)存競(jìng)爭(zhēng)。當(dāng)一個(gè)子 Arena 的堆空間用盡時(shí),會(huì)申請(qǐng)新的堆,并將其加入到鏈表中,就像小型倉(cāng)庫(kù)物資不足時(shí),會(huì)新建倉(cāng)庫(kù)并與原有倉(cāng)庫(kù)連接起來(lái)。
①堆的申請(qǐng)
第一類的堆無(wú)需申請(qǐng),只是調(diào)用brk進(jìn)行堆邊界的拓展即可。這里主要對(duì)第二類堆的申請(qǐng)進(jìn)行說(shuō)明。
- 堆的大小和對(duì)齊:第二類堆在申請(qǐng)時(shí),總是mmap大小為HEAP_MAX_SIZE的內(nèi)存,多出來(lái)的部分將作為預(yù)留空間,防止頻繁申請(qǐng)。并且使其首地址對(duì)齊于HEAP_MAX_SIZE,這可以方便找到堆的起始地址。
 - 什么時(shí)候申請(qǐng)堆:在兩種情況會(huì)進(jìn)行第二類堆的申請(qǐng),第一種情況是在創(chuàng)建子Arena時(shí),會(huì)相應(yīng)地進(jìn)行堆的申請(qǐng)作為該Arena的第一個(gè)堆;第二種情況是在原來(lái)申請(qǐng)的堆已經(jīng)分配完畢時(shí),會(huì)重新進(jìn)行堆的申請(qǐng),并將該堆和原來(lái)的堆通過(guò)鏈表連接起來(lái)。
 - 堆的可用部分:只將用戶所需要的部分分配出去,并使用size記錄,剩下的部分作為預(yù)留。
 
②堆的釋放
這里堆的釋放是指glibc將申請(qǐng)的堆內(nèi)存歸還給內(nèi)核。
對(duì)于第一類堆,可以認(rèn)為只有堆大小的縮減,當(dāng)堆的頂部空閑的內(nèi)存滿足一定條件時(shí),可以通過(guò)brk將堆的邊界下移,top chunk指向地址不變,但大小變小了。
對(duì)于第二類堆,當(dāng)一個(gè)堆中的內(nèi)存已經(jīng)完全被釋放時(shí),就會(huì)將該該堆通過(guò)munmap歸還給內(nèi)核,同時(shí)將top chunk重新指向上一個(gè)堆內(nèi)的可用內(nèi)存地址。
可以這么理解,堆由兩部分組成,一部分是已經(jīng)分配出去的內(nèi)存,另一部分是預(yù)留的內(nèi)存(top,因?yàn)樗偸谴嬖谟诘刂纷罡卟糠郑?,而已?jīng)分配出去的內(nèi)存一部分由free釋放,成為了空閑內(nèi)存(內(nèi)存碎片),由此除預(yù)留部分部分之外,分為兩種內(nèi)存,空閑內(nèi)存和已使用內(nèi)存。
無(wú)論是主 Arena 的堆還是子 Arena 的堆,在內(nèi)存的申請(qǐng)、釋放與管理過(guò)程中,都遵循著一定的機(jī)制。當(dāng)程序通過(guò)malloc函數(shù)申請(qǐng)內(nèi)存時(shí),堆管理器會(huì)首先在堆中查找合適的空閑內(nèi)存塊。如果找到大小合適的空閑內(nèi)存塊,就會(huì)將其分配給程序使用,并將該內(nèi)存塊標(biāo)記為已分配狀態(tài);如果沒(méi)有找到合適的空閑內(nèi)存塊,堆管理器會(huì)根據(jù)情況從操作系統(tǒng)申請(qǐng)更多的內(nèi)存,或者對(duì)已有的內(nèi)存塊進(jìn)行合并和整理,以滿足程序的內(nèi)存需求。
而當(dāng)程序通過(guò)free函數(shù)釋放內(nèi)存時(shí),堆管理器會(huì)將釋放的內(nèi)存塊標(biāo)記為空閑狀態(tài),并嘗試將其與相鄰的空閑內(nèi)存塊進(jìn)行合并,以減少內(nèi)存碎片化,提高內(nèi)存利用率。這就像是在倉(cāng)庫(kù)中,當(dāng)需要領(lǐng)取物資時(shí),會(huì)先查找倉(cāng)庫(kù)中是否有合適的物資,若沒(méi)有則會(huì)申請(qǐng)新的物資;當(dāng)歸還物資時(shí),會(huì)將物資放回倉(cāng)庫(kù),并整理倉(cāng)庫(kù),使物資擺放更加整齊。
1.4堆內(nèi)存管理的分配
glib中堆內(nèi)存分配的基本思路就是,首先找到本線程的Arena,然后優(yōu)先在Arena對(duì)應(yīng)的回收箱中尋找合適大小的內(nèi)存,在內(nèi)存箱中所有內(nèi)存塊均小于所需求的大小,那么就會(huì)去top chunk分割,但是如果top chunk的大小也不足夠,此時(shí)不一定要拓展top,檢查所需的內(nèi)存是否大于128k,若大于,則直接使用系統(tǒng)調(diào)用mmap分配內(nèi)存,如果小于,就進(jìn)行top chunk的拓展,即堆的拓展,拓展完成后,從top chunk中分配內(nèi)存,剩余部分成為新的top chunk。
(1)malloc函數(shù)
malloc 函數(shù)是 C 語(yǔ)言標(biāo)準(zhǔn)庫(kù)中用于動(dòng)態(tài)內(nèi)存分配的核心函數(shù),其函數(shù)原型為:void* malloc(size_t size);。在這個(gè)原型中,size參數(shù)表示需要分配的內(nèi)存塊的字節(jié)數(shù),它是一個(gè)無(wú)符號(hào)整數(shù)類型(size_t),這意味著我們可以根據(jù)實(shí)際需求,精確地指定所需內(nèi)存的大小。
malloc 函數(shù)的主要功能就是從堆內(nèi)存中分配一塊指定大小的連續(xù)內(nèi)存空間,并返回一個(gè)指向該內(nèi)存塊起始地址的指針。這個(gè)返回的指針類型是void*,也就是無(wú)類型指針。這是因?yàn)?malloc 函數(shù)在分配內(nèi)存時(shí),并不知道這塊內(nèi)存將來(lái)會(huì)被用于存儲(chǔ)什么類型的數(shù)據(jù),所以它返回一個(gè)通用的無(wú)類型指針,需要我們?cè)谑褂脮r(shí)將其強(qiáng)制轉(zhuǎn)換為實(shí)際所需的數(shù)據(jù)類型指針。例如,如果我們需要分配一塊內(nèi)存來(lái)存儲(chǔ)整數(shù),就需要將 malloc 返回的指針轉(zhuǎn)換為int*類型;如果要存儲(chǔ)字符,就轉(zhuǎn)換為char*類型。
當(dāng)程序調(diào)用 malloc 函數(shù)請(qǐng)求分配內(nèi)存時(shí),其背后的分配機(jī)制涉及到操作系統(tǒng)與程序之間的協(xié)同工作。操作系統(tǒng)為了有效地管理堆內(nèi)存,通常會(huì)維護(hù)一個(gè)空閑內(nèi)存鏈表,這個(gè)鏈表就像是一個(gè)記錄著所有空閑 “房間”(內(nèi)存塊)的清單。鏈表中的每個(gè)節(jié)點(diǎn)都代表著一塊空閑的內(nèi)存區(qū)域,節(jié)點(diǎn)中包含了該內(nèi)存塊的大小、前后指針等信息,以便操作系統(tǒng)能夠快速地查找和管理這些空閑內(nèi)存。
當(dāng) malloc 函數(shù)被調(diào)用時(shí),操作系統(tǒng)會(huì)按照一定的算法,通常是首次適應(yīng)算法、最佳適應(yīng)算法或最差適應(yīng)算法等,開(kāi)始遍歷這個(gè)空閑內(nèi)存鏈表。以首次適應(yīng)算法為例,操作系統(tǒng)會(huì)從鏈表的頭部開(kāi)始,依次檢查每個(gè)空閑內(nèi)存塊,尋找第一個(gè)大小大于或等于所需分配大小size的內(nèi)存塊。一旦找到這樣的內(nèi)存塊,操作系統(tǒng)就會(huì)將其從空閑鏈表中移除,并根據(jù)需要對(duì)該內(nèi)存塊進(jìn)行分割。如果找到的空閑內(nèi)存塊比請(qǐng)求的size大,那么操作系統(tǒng)會(huì)將多余的部分重新插入到空閑鏈表中,以便后續(xù)的內(nèi)存分配請(qǐng)求使用。而分割出來(lái)的正好滿足size大小的內(nèi)存塊,就會(huì)被標(biāo)記為已分配,并返回其起始地址給程序,這個(gè)地址就是 malloc 函數(shù)的返回值。通過(guò)這樣的方式,malloc 函數(shù)能夠在堆內(nèi)存中靈活地為程序分配所需的內(nèi)存空間,以滿足各種動(dòng)態(tài)內(nèi)存需求。
下面通過(guò)一段簡(jiǎn)單的 C 語(yǔ)言代碼示例,來(lái)展示 malloc 函數(shù)的具體用法。假設(shè)我們要?jiǎng)討B(tài)分配一個(gè)包含 10 個(gè)整數(shù)的數(shù)組,并對(duì)其進(jìn)行初始化和輸出:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *arr;
    int n = 10;
    // 使用malloc分配內(nèi)存,為n個(gè)整數(shù)分配空間
    arr = (int *)malloc(n * sizeof(int));
    // 檢查內(nèi)存分配是否成功
    if (arr == NULL) {
        printf("內(nèi)存分配失敗\n");
        return 1;
    }
    // 初始化數(shù)組
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    // 輸出數(shù)組內(nèi)容
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    // 釋放內(nèi)存,避免內(nèi)存泄漏
    free(arr);
    return 0;
}(2)free函數(shù)
free 函數(shù)與 malloc 函數(shù)緊密配合,是 C 語(yǔ)言中用于釋放動(dòng)態(tài)分配內(nèi)存的關(guān)鍵函數(shù)。其函數(shù)原型為:void free(void *ptr);,這里的ptr參數(shù)是一個(gè)指向先前通過(guò) malloc、calloc 或 realloc 等函數(shù)動(dòng)態(tài)分配的內(nèi)存塊的指針。free 函數(shù)的主要功能就是將ptr所指向的內(nèi)存塊歸還給系統(tǒng),使其重新成為可供分配的空閑內(nèi)存,以便后續(xù)其他內(nèi)存分配請(qǐng)求使用。
當(dāng)程序調(diào)用 free 函數(shù)釋放內(nèi)存時(shí),其內(nèi)部的釋放機(jī)制如下:free 函數(shù)首先會(huì)根據(jù)傳入的指針ptr,找到對(duì)應(yīng)的內(nèi)存塊。在 malloc 分配內(nèi)存時(shí),除了分配用戶請(qǐng)求大小的內(nèi)存空間外,還會(huì)在該內(nèi)存塊的頭部或其他特定位置,記錄一些額外的管理信息,如內(nèi)存塊的大小等。free 函數(shù)通過(guò)這些管理信息,能夠準(zhǔn)確地確定要釋放的內(nèi)存塊的邊界和大小。然后,free 函數(shù)會(huì)將該內(nèi)存塊標(biāo)記為空閑狀態(tài),并將其重新插入到操作系統(tǒng)維護(hù)的空閑內(nèi)存鏈表中。
如果相鄰的內(nèi)存塊也是空閑狀態(tài),free 函數(shù)通常會(huì)將它們合并成一個(gè)更大的空閑內(nèi)存塊,這一過(guò)程被稱為內(nèi)存合并。內(nèi)存合并可以有效地減少內(nèi)存碎片的產(chǎn)生,提高內(nèi)存的利用率。例如,在一個(gè)頻繁進(jìn)行內(nèi)存分配和釋放的程序中,如果不進(jìn)行內(nèi)存合并,隨著時(shí)間的推移,內(nèi)存中可能會(huì)出現(xiàn)大量零散的小空閑內(nèi)存塊,這些小內(nèi)存塊由于無(wú)法滿足較大的內(nèi)存分配請(qǐng)求,而導(dǎo)致內(nèi)存資源的浪費(fèi)。通過(guò)內(nèi)存合并,這些相鄰的小空閑內(nèi)存塊可以合并成一個(gè)較大的空閑內(nèi)存塊,從而提高內(nèi)存的使用效率。
接著上面 malloc 函數(shù)的示例代碼,我們來(lái)看一下 free 函數(shù)的使用:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *arr;
    int n = 10;
    // 使用malloc分配內(nèi)存,為n個(gè)整數(shù)分配空間
    arr = (int *)malloc(n * sizeof(int));
    // 檢查內(nèi)存分配是否成功
    if (arr == NULL) {
        printf("內(nèi)存分配失敗\n");
        return 1;
    }
    // 初始化數(shù)組
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    // 輸出數(shù)組內(nèi)容
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    // 釋放內(nèi)存,避免內(nèi)存泄漏
    free(arr);
    // 將指針置空,避免懸空指針
    arr = NULL;
    return 0;
}在這段代碼中,當(dāng)我們使用 malloc 函數(shù)分配內(nèi)存并完成對(duì)數(shù)組的操作后,調(diào)用 free (arr) 來(lái)釋放之前分配的內(nèi)存。需要特別注意的是,在調(diào)用 free 函數(shù)之后,我們將指針arr賦值為NULL 。這是一個(gè)非常重要的操作,因?yàn)槿绻粚⒅羔樦每?,arr就會(huì)成為一個(gè)懸空指針(Dangling Pointer)。懸空指針指向的是一塊已經(jīng)被釋放的內(nèi)存,繼續(xù)使用懸空指針進(jìn)行內(nèi)存訪問(wèn),會(huì)導(dǎo)致未定義行為,可能引發(fā)程序崩潰、數(shù)據(jù)損壞等嚴(yán)重問(wèn)題。將指針置空后,就可以避免不小心對(duì)已釋放內(nèi)存的訪問(wèn),提高程序的穩(wěn)定性和安全性。
二、delete 釋放內(nèi)存原理剖析
2.1 delete 的基本用法
在 C++ 的世界里,delete 是釋放內(nèi)存的關(guān)鍵操作符,就像一把神奇的 “內(nèi)存掃帚”,能夠清掃程序中不再需要的內(nèi)存空間。不過(guò),這把 “掃帚” 的使用方法可是有不少講究的。
(1)單個(gè)對(duì)象的 delete 操作
當(dāng)我們使用 new 操作符在堆上創(chuàng)建一個(gè)單個(gè)對(duì)象時(shí),就需要使用 delete 來(lái)釋放它所占用的內(nèi)存。來(lái)看一段簡(jiǎn)單的代碼示例:
#include <iostream>
int main() {
    int* ptr = new int;
    *ptr = 10;
    std::cout << "The value of ptr is: " << *ptr << std::endl;
    delete ptr;
    return 0;
}在這段代碼中,int* ptr = new int;這行代碼在堆上分配了一個(gè)整型大小的內(nèi)存空間,并將其地址賦給了指針ptr。接著,我們給這個(gè)內(nèi)存空間賦值為 10。當(dāng)我們不再需要這個(gè)內(nèi)存空間時(shí),就使用delete ptr;來(lái)釋放它。這樣,這塊內(nèi)存就可以被系統(tǒng)重新分配給其他需要的地方,就像把一個(gè)不再使用的物品放回倉(cāng)庫(kù),以便其他人可以再次使用。
(2)數(shù)組對(duì)象的 delete [] 操作
如果我們創(chuàng)建的是一個(gè)數(shù)組對(duì)象,情況就稍有不同了,這時(shí)需要使用delete[]來(lái)釋放內(nèi)存。delete[]專門用于釋放由new[]申請(qǐng)的動(dòng)態(tài)內(nèi)存,也就是對(duì)象數(shù)組指針指向的內(nèi)存。例如:
#include <iostream>
int main() {
    int* arr = new int[5];
    for (int i = 0; i < 5; ++i) {
        arr[i] = i + 1;
    }
    std::cout << "The elements of the array are: ";
    for (int i = 0; i < 5; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    delete[] arr;
    return 0;
}在這個(gè)例子中,int* arr = new int[5];創(chuàng)建了一個(gè)包含 5 個(gè)整數(shù)的數(shù)組。然后我們對(duì)數(shù)組進(jìn)行了初始化,并輸出了數(shù)組的元素。最后,使用delete[] arr;來(lái)釋放整個(gè)數(shù)組所占用的內(nèi)存空間。這里使用delete[]而不是delete是非常重要的,因?yàn)閐elete[]會(huì)正確地調(diào)用數(shù)組中每個(gè)元素的析構(gòu)函數(shù)(如果是自定義類型的數(shù)組),并釋放整個(gè)數(shù)組的內(nèi)存。
如果錯(cuò)誤地使用delete來(lái)釋放數(shù)組內(nèi)存,雖然對(duì)于基本數(shù)據(jù)類型的數(shù)組可能不會(huì)立即出現(xiàn)明顯的問(wèn)題,但對(duì)于自定義數(shù)據(jù)類型的數(shù)組,就只會(huì)調(diào)用數(shù)組第一個(gè)對(duì)象的析構(gòu)函數(shù),而其他對(duì)象的資源可能無(wú)法正確釋放,從而導(dǎo)致內(nèi)存泄漏等嚴(yán)重問(wèn)題。就好比你要拆除一排房子(數(shù)組),delete[]會(huì)一間一間地拆除(調(diào)用每個(gè)對(duì)象的析構(gòu)函數(shù)),而delete可能只拆除第一間房子,剩下的房子就會(huì)一直留在那里,占用著寶貴的土地資源(內(nèi)存) 。
2.2 delete 簡(jiǎn)單類型原理
當(dāng)delete用于釋放簡(jiǎn)單類型(如基本數(shù)據(jù)類型int、char、double等)的內(nèi)存時(shí),其原理相對(duì)簡(jiǎn)單。在這種情況下,delete實(shí)際上默認(rèn)只是調(diào)用了free函數(shù)來(lái)釋放內(nèi)存 。這是因?yàn)楹?jiǎn)單類型沒(méi)有復(fù)雜的資源管理需求,它們只占用固定大小的內(nèi)存空間,不需要額外的清理操作。例如:
int* p = new int;
*p = 10;
delete p;在這段代碼中,new int分配了一個(gè)int類型大小的內(nèi)存空間,并返回指向該空間的指針p。當(dāng)執(zhí)行delete p時(shí),delete會(huì)調(diào)用free函數(shù),將p所指向的內(nèi)存歸還給系統(tǒng),從而完成內(nèi)存的釋放。從功能上來(lái)說(shuō),在釋放簡(jiǎn)單類型內(nèi)存時(shí),delete和free的作用是類似的。但需要注意的是,delete是 C++ 的運(yùn)算符,而free是 C 語(yǔ)言標(biāo)準(zhǔn)庫(kù)函數(shù),它們的使用場(chǎng)景和語(yǔ)義還是存在一些差異的,在 C++ 中,對(duì)于由new分配的內(nèi)存,應(yīng)該使用delete來(lái)釋放,以保證代碼的一致性和安全性。
2.3 delete復(fù)雜類型原理
當(dāng)面對(duì)復(fù)雜類型(如自定義類、結(jié)構(gòu)體等)時(shí),delete的工作原理就變得更加復(fù)雜和精細(xì)。對(duì)于復(fù)雜類型,delete在釋放內(nèi)存時(shí),首先會(huì)調(diào)用對(duì)象的析構(gòu)函數(shù),然后再調(diào)用operator delete來(lái)釋放內(nèi)存。析構(gòu)函數(shù)在這個(gè)過(guò)程中起著至關(guān)重要的作用,它負(fù)責(zé)清理對(duì)象內(nèi)部所占用的資源,比如動(dòng)態(tài)分配的內(nèi)存、打開(kāi)的文件句柄、網(wǎng)絡(luò)連接等。例如我們有一個(gè)自定義類MyClass:
class MyClass {
public:
    MyClass() {
        // 構(gòu)造函數(shù),初始化資源
        data = new int[10];
        std::cout << "MyClass constructor" << std::endl;
    }
    ~MyClass() {
        // 析構(gòu)函數(shù),釋放資源
        delete[] data;
        std::cout << "MyClass destructor" << std::endl;
    }
private:
    int* data;
};當(dāng)我們使用new創(chuàng)建MyClass對(duì)象并使用delete釋放時(shí):
MyClass* obj = new MyClass;
delete obj;在執(zhí)行delete obj時(shí),首先會(huì)調(diào)用MyClass的析構(gòu)函數(shù)~MyClass()。在析構(gòu)函數(shù)中,我們看到delete[] data;這一行代碼,它負(fù)責(zé)釋放MyClass對(duì)象內(nèi)部動(dòng)態(tài)分配的數(shù)組data。只有在析構(gòu)函數(shù)完成對(duì)對(duì)象內(nèi)部資源的清理后,delete才會(huì)調(diào)用operator delete來(lái)釋放obj所指向的內(nèi)存空間,將其歸還給系統(tǒng)堆管理器。如果在釋放復(fù)雜類型對(duì)象時(shí)不調(diào)用析構(gòu)函數(shù),那么對(duì)象內(nèi)部的資源就無(wú)法得到正確釋放,從而導(dǎo)致內(nèi)存泄漏。這也是delete在處理復(fù)雜類型時(shí)與簡(jiǎn)單類型的最大區(qū)別,它通過(guò)析構(gòu)函數(shù)實(shí)現(xiàn)了對(duì)復(fù)雜對(duì)象生命周期的完整管理,確保對(duì)象占用的所有資源都能被妥善清理和釋放。
2.4 delete [] 與數(shù)組內(nèi)存釋放
當(dāng)我們使用new[]來(lái)分配數(shù)組內(nèi)存時(shí),情況又有所不同。為了能夠正確地釋放數(shù)組中每個(gè)元素的資源并回收整個(gè)數(shù)組的內(nèi)存,C++ 采用了一種巧妙的機(jī)制。在使用new[]分配數(shù)組內(nèi)存時(shí),實(shí)際上會(huì)多分配 4 個(gè)字節(jié)(在 32 位系統(tǒng)下,64 位系統(tǒng)可能不同),這 4 個(gè)字節(jié)被用來(lái)記錄數(shù)組的大小。例如:
int* arr = new int[5];在這個(gè)例子中,new int[5]不僅分配了 5 個(gè)int類型元素所需的內(nèi)存空間,還額外在前面多分配了 4 個(gè)字節(jié)來(lái)保存數(shù)組的大小信息 5。
當(dāng)使用delete[]來(lái)釋放數(shù)組內(nèi)存時(shí),它會(huì)首先讀取這 4 個(gè)字節(jié)中記錄的數(shù)組大小信息。然后,delete[]會(huì)根據(jù)這個(gè)大小信息,依次逆序調(diào)用數(shù)組中每個(gè)元素的析構(gòu)函數(shù)(如果是自定義類型的數(shù)組),清理每個(gè)元素所占用的資源。最后,delete[]再調(diào)用operator delete[]函數(shù)(其內(nèi)部調(diào)用free函數(shù)),釋放整個(gè)數(shù)組所占用的內(nèi)存空間,包括前面多分配的 4 個(gè)字節(jié)。比如:
delete[] arr;delete[] arr通過(guò)讀取前面 4 字節(jié)記錄的數(shù)組大小,依次調(diào)用每個(gè)元素的析構(gòu)函數(shù)(如果有),最后釋放整個(gè)數(shù)組內(nèi)存。如果錯(cuò)誤地使用delete而不是delete[]來(lái)釋放數(shù)組內(nèi)存,對(duì)于自定義類型的數(shù)組,就只會(huì)調(diào)用數(shù)組第一個(gè)元素的析構(gòu)函數(shù),而其他元素的資源無(wú)法得到正確釋放,從而導(dǎo)致內(nèi)存泄漏 。所以,在釋放數(shù)組內(nèi)存時(shí),一定要使用delete[],以確保內(nèi)存的正確釋放和資源的有效回收 。
三、delete 如何知曉釋放內(nèi)存大小
3.1 new [] 分配內(nèi)存的特殊處理
當(dāng)我們使用new[]來(lái)分配數(shù)組內(nèi)存時(shí),C++ 編譯器會(huì)進(jìn)行一些特殊的處理。在實(shí)際存儲(chǔ)數(shù)組數(shù)據(jù)的內(nèi)存塊之前,會(huì)額外多分配 4 個(gè)字節(jié)的空間 。這 4 個(gè)字節(jié)可不是隨便分配的,它們有著至關(guān)重要的作用,那就是用來(lái)記錄數(shù)組中元素的個(gè)數(shù)。比如我們使用int* arr = new int[10];來(lái)分配一個(gè)包含 10 個(gè)int類型元素的數(shù)組,在內(nèi)存中,實(shí)際的布局是這樣的:首先是額外分配的 4 個(gè)字節(jié),它們存儲(chǔ)著數(shù)值 10,表示數(shù)組元素的個(gè)數(shù);接著才是真正用于存儲(chǔ) 10 個(gè)int類型數(shù)據(jù)的內(nèi)存空間。這種在分配內(nèi)存時(shí)預(yù)留空間記錄數(shù)組大小的方式,為后續(xù)delete[]釋放內(nèi)存提供了關(guān)鍵的信息。
3.2 delete [] 讀取內(nèi)存大小信息
當(dāng)我們使用delete[]來(lái)釋放通過(guò)new[]分配的數(shù)組內(nèi)存時(shí),它會(huì)從內(nèi)存塊的起始位置向前偏移 4 個(gè)字節(jié)(因?yàn)橹皀ew[]分配內(nèi)存時(shí),在實(shí)際數(shù)據(jù)內(nèi)存塊前多分配了 4 個(gè)字節(jié)來(lái)記錄數(shù)組元素個(gè)數(shù)) 。通過(guò)讀取這 4 個(gè)字節(jié)中的內(nèi)容,delete[]就能準(zhǔn)確得知數(shù)組中元素的個(gè)數(shù)。知道了元素個(gè)數(shù),delete[]就可以按照正確的次數(shù)調(diào)用數(shù)組中每個(gè)元素的析構(gòu)函數(shù),將每個(gè)元素占用的資源都清理干凈。
比如對(duì)于一個(gè)包含自定義類型對(duì)象的數(shù)組,每個(gè)對(duì)象在創(chuàng)建時(shí)可能分配了一些資源,如動(dòng)態(tài)分配的內(nèi)存、打開(kāi)的文件句柄等,通過(guò)調(diào)用析構(gòu)函數(shù),這些資源都能得到妥善釋放。在完成所有元素析構(gòu)函數(shù)的調(diào)用后,delete[]再將整個(gè)內(nèi)存塊(包括之前記錄元素個(gè)數(shù)的 4 個(gè)字節(jié)和存儲(chǔ)數(shù)據(jù)的內(nèi)存空間)歸還給系統(tǒng),完成內(nèi)存的釋放操作。
3.3示例代碼演示
下面通過(guò)一段具體的代碼來(lái)直觀地展示new[]分配內(nèi)存和delete[]釋放內(nèi)存的過(guò)程:
#include <iostream>
class MyClass {
public:
    MyClass() {
        std::cout << "MyClass constructor" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destructor" << std::endl;
    }
};
int main() {
    MyClass* arr = new MyClass[3];// 使用new[]分配包含3個(gè)MyClass對(duì)象的數(shù)組
    // 這里可以對(duì)arr數(shù)組進(jìn)行操作
    delete[] arr; // 使用delete[]釋放數(shù)組內(nèi)存
    return 0;
}在這段代碼中,new MyClass[3]會(huì)分配一段內(nèi)存,在實(shí)際存儲(chǔ) 3 個(gè)MyClass對(duì)象的內(nèi)存塊前,會(huì)有 4 個(gè)字節(jié)用于記錄元素個(gè)數(shù) 3。當(dāng)執(zhí)行delete[] arr時(shí),它會(huì)先讀取這 4 個(gè)字節(jié)中的數(shù)字 3,然后依次調(diào)用 3 次MyClass的析構(gòu)函數(shù),輸出 3 次MyClass destructor,最后將整個(gè)內(nèi)存塊釋放。
如果我們錯(cuò)誤地使用delete arr(而不是delete[] arr)來(lái)釋放這個(gè)數(shù)組內(nèi)存,就會(huì)導(dǎo)致程序出現(xiàn)未定義行為,可能引發(fā)程序崩潰,因?yàn)閐elete不會(huì)去讀取前面記錄元素個(gè)數(shù)的 4 個(gè)字節(jié),也就無(wú)法正確調(diào)用每個(gè)元素的析構(gòu)函數(shù),從而造成內(nèi)存泄漏和資源未正確釋放的問(wèn)題 。通過(guò)這個(gè)示例代碼,我們可以清晰地看到new[]和delete[]在處理數(shù)組內(nèi)存時(shí),是如何通過(guò)記錄和讀取內(nèi)存大小信息來(lái)實(shí)現(xiàn)正確的內(nèi)存管理的。
四、delete 釋放內(nèi)存的常見(jiàn)問(wèn)題與解決方案
4.1內(nèi)存泄漏
在使用delete釋放內(nèi)存時(shí),最常見(jiàn)的問(wèn)題之一就是內(nèi)存泄漏。當(dāng)我們使用new分配了內(nèi)存,卻忘記使用delete來(lái)釋放它,這些未被釋放的內(nèi)存就會(huì)一直占用系統(tǒng)資源,隨著程序的運(yùn)行,占用的內(nèi)存越來(lái)越多,最終可能導(dǎo)致系統(tǒng)內(nèi)存不足,程序運(yùn)行緩慢甚至崩潰。比如下面這段代碼:
void memoryLeakExample() {
    int* ptr = new int;
    // 一些操作
    // 忘記釋放ptr指向的內(nèi)存
}在這個(gè)函數(shù)中,我們使用new分配了一個(gè)int類型大小的內(nèi)存,但在函數(shù)結(jié)束時(shí),沒(méi)有調(diào)用delete來(lái)釋放這塊內(nèi)存,從而導(dǎo)致內(nèi)存泄漏。
為了避免內(nèi)存泄漏,我們可以使用智能指針。C++ 標(biāo)準(zhǔn)庫(kù)提供了幾種智能指針,如std::unique_ptr、std::shared_ptr和std::weak_ptr 。以std::unique_ptr為例,它采用獨(dú)占所有權(quán)的方式管理對(duì)象,當(dāng)std::unique_ptr對(duì)象被銷毀時(shí),它所指向的對(duì)象也會(huì)被自動(dòng)銷毀。使用智能指針可以大大簡(jiǎn)化內(nèi)存管理,減少人為忘記釋放內(nèi)存的風(fēng)險(xiǎn) 。修改后的代碼如下:
#include <memory>
void properMemoryManagement() {
    std::unique_ptr<int> ptr = std::make_unique<int>();
    // 一些操作
    // 無(wú)需手動(dòng)調(diào)用delete,離開(kāi)作用域時(shí)ptr會(huì)自動(dòng)釋放內(nèi)存
}在現(xiàn)代 C++ 編程中,推薦使用智能指針來(lái)管理動(dòng)態(tài)內(nèi)存,這樣可以提高代碼的安全性和可靠性。
4.2懸掛指針
當(dāng)我們釋放了內(nèi)存,但指向該內(nèi)存的指針沒(méi)有被置為空指針(nullptr)時(shí),就會(huì)產(chǎn)生懸掛指針。懸掛指針指向的是一塊已經(jīng)被釋放的內(nèi)存,再次使用懸掛指針會(huì)導(dǎo)致未定義行為,可能引發(fā)程序崩潰或數(shù)據(jù)損壞等嚴(yán)重問(wèn)題。例如:
int* ptr = new int;
delete ptr;
// ptr現(xiàn)在是一個(gè)懸掛指針,如果再次使用可能導(dǎo)致未定義行為
std::cout << *ptr;為了避免懸掛指針問(wèn)題,在釋放內(nèi)存后,我們應(yīng)該立即將指針置為nullptr,這樣可以明確表示該指針不再指向有效的內(nèi)存。修改后的代碼如下:
int* ptr = new int;
delete ptr;
ptr = nullptr;另外,使用智能指針也可以有效避免懸掛指針問(wèn)題,因?yàn)橹悄苤羔樤趯?duì)象生命周期結(jié)束時(shí)會(huì)自動(dòng)釋放內(nèi)存,并且不會(huì)產(chǎn)生懸掛指針。
4.3誤用 delete 與 delete []
在 C++ 中,使用delete和delete[]時(shí)需要特別小心,因?yàn)樗鼈兊氖褂脠?chǎng)景是不同的。delete用于釋放單個(gè)對(duì)象的內(nèi)存,而delete[]用于釋放數(shù)組的內(nèi)存。如果使用delete來(lái)釋放數(shù)組內(nèi)存,或者使用delete[]來(lái)釋放單個(gè)對(duì)象的內(nèi)存,都會(huì)導(dǎo)致未定義行為 。例如:
int* arr = new int[5];
delete arr; // 錯(cuò)誤,應(yīng)使用delete[]在這段代碼中,我們使用new[]分配了一個(gè)包含 5 個(gè)int類型元素的數(shù)組,但卻使用delete來(lái)釋放內(nèi)存,這是錯(cuò)誤的做法。正確的做法是使用delete[]來(lái)釋放數(shù)組內(nèi)存:
int* arr = new int[5];
delete[] arr;同樣,如果使用delete[]來(lái)釋放單個(gè)對(duì)象的內(nèi)存,也是錯(cuò)誤的:
int* num = new int;
delete[] num; // 錯(cuò)誤,應(yīng)使用delete為了避免這類錯(cuò)誤,我們?cè)诜峙鋬?nèi)存時(shí)就要明確是分配單個(gè)對(duì)象還是數(shù)組,然后在釋放內(nèi)存時(shí)使用與之匹配的操作符。
4.4重復(fù)釋放內(nèi)存
對(duì)同一塊內(nèi)存多次執(zhí)行delete或delete[]操作會(huì)導(dǎo)致嚴(yán)重的錯(cuò)誤,因?yàn)檫@塊內(nèi)存可能已經(jīng)被釋放,再次釋放會(huì)導(dǎo)致未定義行為,可能引發(fā)程序崩潰。例如:
int* ptr = new int;
delete ptr;
delete ptr; // 重復(fù)釋放,錯(cuò)誤為了避免重復(fù)釋放內(nèi)存,一種方法是在釋放內(nèi)存后將指針置為nullptr,這樣再次調(diào)用delete時(shí),delete會(huì)檢查指針是否為nullptr,如果是nullptr,則不會(huì)進(jìn)行釋放操作,從而避免錯(cuò)誤。修改后的代碼如下:
int* ptr = new int;
delete ptr;
ptr = nullptr;
delete ptr; // 不會(huì)產(chǎn)生錯(cuò)誤,因?yàn)閜tr是nullptr另一種更好的方法是使用智能指針,智能指針會(huì)自動(dòng)管理內(nèi)存的生命周期,避免手動(dòng)管理內(nèi)存帶來(lái)的重復(fù)釋放等問(wèn)題 。例如使用std::unique_ptr:
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>();
// 無(wú)需擔(dān)心重復(fù)釋放問(wèn)題,離開(kāi)作用域時(shí)ptr會(huì)自動(dòng)釋放內(nèi)存通過(guò)以上幾種常見(jiàn)問(wèn)題的分析和解決方案的介紹,希望大家在使用delete釋放內(nèi)存時(shí)能夠更加謹(jǐn)慎,避免這些問(wèn)題的出現(xiàn),編寫出更加健壯、可靠的 C++ 程序 。
五、Linux內(nèi)存管理面試題
面試題 1:delete 釋放內(nèi)存時(shí),它是如何知曉需釋放的內(nèi)存大小的?
答案:系統(tǒng)通常會(huì)在分配內(nèi)存時(shí),于內(nèi)存首地址附近記錄本次分配的內(nèi)存大小等信息。當(dāng)使用 delete 時(shí),其能依據(jù)這些隱藏記錄定位需釋放的正確范圍。
面試題 2:用 new 分配數(shù)組內(nèi)存,為何需用 delete[] 釋放?
答案:對(duì)于通過(guò) new[] 分配的數(shù)組,delete[] 能識(shí)別內(nèi)存中存儲(chǔ)的數(shù)組長(zhǎng)度信息,針對(duì)自定義類型,其會(huì)正確調(diào)用數(shù)組每個(gè)元素的析構(gòu)函數(shù),之后釋放整塊內(nèi)存。若誤用 delete,可能僅調(diào)用首個(gè)元素析構(gòu)函數(shù)并釋放部分內(nèi)存,引發(fā)內(nèi)存泄漏或未定義行為。
面試題 3:delete 和 free 釋放內(nèi)存的方式不同,delete 不必手動(dòng)指定大小,free 卻需 malloc 明確大小,為什么?
答案:new 和 delete 是 C++ 操作符,分配時(shí)會(huì)依據(jù)數(shù)據(jù)類型記錄尺寸信息。malloc 和 free 是 C 標(biāo)準(zhǔn)庫(kù)函數(shù),malloc 按用戶傳遞的字節(jié)數(shù)分配,釋放時(shí)無(wú)其他存儲(chǔ)大小的默認(rèn)機(jī)制,需開(kāi)發(fā)者確保邏輯合理,自行匹配已分配的內(nèi)存大小。
面試題 4:若不知道是用 new 還是 new[] 分配的內(nèi)存,該怎么釋放?
答案:必須確定分配方式以對(duì)應(yīng)釋放,否則行為未定義。編程實(shí)踐應(yīng)遵循清晰的內(nèi)存管理規(guī)范,或者用智能指針等自動(dòng)內(nèi)存管理工具規(guī)避這種不確定性。
面試題 5:delete 釋放內(nèi)存后,內(nèi)存大小記錄信息會(huì)被怎么處理?
答案:delete 底層常調(diào)用類似 operator delete 函數(shù)(默認(rèn)實(shí)現(xiàn)會(huì)關(guān)聯(lián) free 等),釋放后,內(nèi)存大小記錄信息所在區(qū)域歸還給空閑內(nèi)存管理系統(tǒng)。其可能被覆蓋重寫,供后續(xù)分配操作重新利用該內(nèi)存空間。
面試題 6:delete 能釋放 malloc 分配的內(nèi)存嗎,它能正確獲取到其內(nèi)存大小嗎?
答案:不能。malloc 和 free 需明確大小,無(wú)適配 delete 獲取大小的內(nèi)置機(jī)制。new/delete 針對(duì) C++ 類型管理含析構(gòu)等邏輯,與 malloc/free 內(nèi)存布局和操作邏輯不同,交叉使用會(huì)引發(fā)未定義行為。
面試題 7:delete 不知道內(nèi)存大小時(shí)能否安全釋放基本數(shù)據(jù)類型數(shù)組?
答案:使用 delete[] 能安全釋放?;绢愋蜔o(wú)析構(gòu)函數(shù),delete 或 delete[] 都可釋放內(nèi)存空間。不過(guò),遵循規(guī)范應(yīng)始終用 delete[] 匹配 new[],以避免基本類型后期被替換成自定義類型后出現(xiàn)風(fēng)險(xiǎn)。
面試題 8:delete 釋放內(nèi)存時(shí),不知道內(nèi)存大小,遇到內(nèi)存碎片會(huì)影響釋放嗎?
答案:一般不受影響。內(nèi)存碎片指空閑內(nèi)存分散成小碎片區(qū)域,其影響內(nèi)存分配找連續(xù)可用塊的效率。delete 釋放按已記錄的分配范圍處理,釋放后內(nèi)存管理系統(tǒng)或嘗試合并相鄰空閑區(qū)域減少碎片。
面試題 9:若重載 operator new 改變內(nèi)存分配邏輯,delete 還能正常獲取內(nèi)存大小并釋放嗎?
答案:若重載 operator new,需對(duì)應(yīng)重載 operator delete 維持匹配邏輯。若按特殊方式存儲(chǔ)內(nèi)存大小,重載的 operator delete 需實(shí)現(xiàn)對(duì)應(yīng)讀取邏輯釋放。否則默認(rèn) delete 處理可能無(wú)法獲取準(zhǔn)確大小,觸發(fā)未定義行為。
面試題 10:delete 是怎么知道釋放自定義類型對(duì)象內(nèi)存大小的?
答案:new 分配自定義類型內(nèi)存時(shí),依其聲明結(jié)構(gòu)計(jì)算大小。delete 依賴 new 階段存儲(chǔ)的對(duì)應(yīng)類型內(nèi)存分配信息,調(diào)用析構(gòu)函數(shù)后,釋放與該類型尺寸匹配的由 new 保留的整塊內(nèi)存。















 
 
 

 
 
 
 