進一步理解C++中的堆(Heap)
最近的專案涉及到Heap Corruption的問題,所以對堆要有更深的理解。
程序初始化時會被分配一個預設大小為1M的預設堆,這個堆會被很多重要的函式呼叫,比如當我們呼叫ANSI版本的某些函式時,它們的Unicode版本字串就會存於其中。若應用程式中有多個執行緒都用到了預設堆,那麼會有機制使得同時只能有一執行緒能在預設堆中進行操作。預設堆的分配和銷燬都是由系統控制的,但是我們可以通過GetPreocessHeap()來得到本程序的預設堆控制代碼。
常用的分配函式有VirtualAlloc和HeapAlloc.VirtualAlloc請求4K為邊界的整塊虛擬記憶體,HeapAlloc分配任意大小的記憶體塊。但後者是依賴前者實現的。也就是說在作業系統的層面上管理記憶體的最小單位是4K。要實現更小的記憶體管理(即HeapAlloc),需要使用者態的程式自己去分配,比如說Windows的HeapManager。在分配時先用分配足夠大的4K倍數的空間,再去進行內部的分配和回收。一般而言,對於小於1M的地址空間,我們一般使用HeapCreate(),但是更大的話,會傾向於用VirtualAlloc()在虛擬記憶體中分配。
以Alloc結尾的函式都只是分配堆空間,而真正的要去建立一個堆,要使用HeapCreate()。HeapCreate()會返回一個新堆的控制代碼,而各種alloc函式可以利用這個控制代碼在不同的堆空間上進行記憶體分配。在不同的堆上進行alloc可以有效的避免記憶體碎片的問題,因為當某一個堆中的內容不再需要時,我們可以將這個堆整個的HeapDestroy()掉。但是,若各種資料都存於一個堆中,則要Destroy整個堆必須保證所有的資料都不再需要。
當我們先把一個地址空間HeapFree之後,若HeapManager沒有進行VirtualFree的操作,再次訪問該地址作業系統並不會報錯。因為以HeapFree和HeapAlloc都是由HeapManager來控制的,而作業系統一般情況下的界定粒度為4k。如果只是在HeapManager的控制範圍內發生了越界,作業系統可能並不會認為這有什麼錯誤。(因為沒有在以4k為粒度的記憶體上越界)。
在各種create中,經常會有HEAP_NOSERIALIZE這個標誌,它的作用是對堆進行執行緒訪問控制。若這個標誌被加到引數中,那麼同一時刻,可以有多個執行緒對同一個堆進行堆操作。反之亦然。如果我們不用這個引數,還有兩個可用函式HeapLock()和HeapUnlock()達到類似效果。