堆 前面我們說過堆非常適合分配大量的小型數(shù)據(jù)。使用堆可以讓程序員專心解決手頭的問題,而不必理會分配粒度和頁面邊界之類的事情。因此堆是管理鏈表和數(shù)的最佳方式。但是堆進(jìn)行內(nèi)存分配和釋放時的速度比其他方式都慢,而且無法對物理存儲器的調(diào)撥和撤銷調(diào)撥進(jìn)行控制。
什么是堆?
在系統(tǒng)內(nèi)部堆就是一塊預(yù)定的地址空間區(qū)域。剛開始堆的大部分頁面都沒有調(diào)撥物理存儲器。隨著我們不斷的從堆中分配內(nèi)存,堆管理器會給堆調(diào)撥越來越多的物理存儲器。這些物理存儲器始終是從頁交換文件中分配的。釋放堆中的內(nèi)存時,堆管理器會撤銷已調(diào)撥的物理存儲器。
進(jìn)程默認(rèn)堆。
進(jìn)程初始化時,系統(tǒng)會在進(jìn)程的地址空間中創(chuàng)建一個堆。這個堆被稱為進(jìn)程的默認(rèn)堆。默認(rèn)情況下,這個堆的地址空間區(qū)域大小是1MB。程序員可以控制這個大小。我們可以在創(chuàng)建應(yīng)用程序時用/HEAP連接器開關(guān)來改變默認(rèn)堆的大小。由于DLL沒有與之關(guān)聯(lián)的堆,因此在創(chuàng)建DLL時,不應(yīng)該使用/HEAP開關(guān)。
[html] view plaincopy
由于許多Windows函數(shù)會用到進(jìn)程默認(rèn)堆,因此對默認(rèn)堆的訪問必須一次進(jìn)行。系統(tǒng)會保證在任何情況下只讓一個線程從默認(rèn)堆中分配或釋放內(nèi)存塊。如果應(yīng)用程序只有一個線程,而我們又希望以最快的方式訪問堆,我們應(yīng)該創(chuàng)建自己的堆,而不要使用默認(rèn)堆。
默認(rèn)堆由系統(tǒng)創(chuàng)建和釋放,我們無法銷毀默認(rèn)堆。每個堆都有一個標(biāo)識自己的句柄,所有分配和釋放內(nèi)存塊的堆函數(shù)都會在參數(shù)中使用到這個堆句柄。我們可以調(diào)用GetProcessHeap來得到默認(rèn)堆的句柄。
[cpp] view plaincopy
創(chuàng)建額外堆的時機(jī):
一:對數(shù)據(jù)保護(hù)。創(chuàng)建兩個或多個獨立的堆,每個堆保存不同的結(jié)構(gòu),對兩個堆分別操作,可以使問題局部化。
二:更有效的內(nèi)存管理。創(chuàng)建額外的堆,管理同樣大小的對象。這樣在釋放一個空間后可以剛好容納另一個對象。
三:內(nèi)存訪問局部化。將需要同時訪問的數(shù)據(jù)放在相鄰的區(qū)域,可以減少缺頁中斷的次數(shù)。
四:避免線程同步開銷。默認(rèn)堆的訪問是依次進(jìn)行的。堆函數(shù)必須執(zhí)行額外的代碼來保證線程安全性。通過創(chuàng)建額外的堆可以避免同步開銷。
五:快速釋放。我們可以直接釋放整個堆而不需要手動的釋放每個內(nèi)存塊。這不但極其方便,而且還可以更快的運行。
創(chuàng)建額外的堆
我們可以調(diào)用HeapCreate來創(chuàng)建額外的堆:
[cpp] view plaincopy
fdwOptions表示對堆的操作該如何進(jìn)行??梢詡魅?span style="font-family:Times New Roman">0, HEAP_NO_SERIALIZE, HEAP_GENERATE_EXCEPTIONS,HEAP_CREATE_ENABLE_EXECUTE或這些標(biāo)志的組合。 HEAP_NO_SERIALIZE告訴堆管理器堆管理器不負(fù)責(zé)堆的線程安全性。對堆的線程安全性的控制由程序員控制。
HEAP_GENERATE_EXCEPTIONS標(biāo)志告訴系統(tǒng),每當(dāng)在堆中分配或重新分配內(nèi)存塊失敗時拋出一個異常。用來通知應(yīng)用程序有錯誤發(fā)生。
dwInitialSize表示一開始要調(diào)撥給堆的字節(jié)數(shù)。如果需要HeapCreate會把這個值向上取整到cpu頁面大小的整數(shù)倍。
dwMaximumSize表示堆所能增長到的最大大小。即系統(tǒng)為堆所預(yù)定的地址空間的最大大小。如果試圖分配的內(nèi)存塊超過最大大小,分配操作會失敗。如果dwMaximumSize為0,則表明創(chuàng)建的堆是可增長的,沒有一個指定上限。
函數(shù)執(zhí)行成功HeapCreate會返回一個句柄,標(biāo)識了新創(chuàng)建的堆。
堆創(chuàng)建后,需要從堆中分配內(nèi)存時,要調(diào)用HeapAlloc函數(shù):
[cpp] view plaincopy
hHeap是一個堆句柄,表示要從哪個堆分配內(nèi)存。
fdwFlags用來執(zhí)行一些標(biāo)志。這些標(biāo)志會對分配產(chǎn)生一些影響。總共有三個標(biāo)志:
HEAP_ZERO_MEMORY,HEAP_GENERATE_EXCEPTIONS和HEAP_NO_SERIALIZE。
HEAP_ZERO_MEMORY會讓HeapAlloc返回之前把內(nèi)存塊的內(nèi)容都清0 。
HEAP_GENERATE_EXCEPTIONS用來告訴系統(tǒng)如果堆中沒有足夠的內(nèi)存來滿足分配請求,此次調(diào)用的 HeapAlloc應(yīng)拋出異常??梢栽趧?chuàng)建堆時指定這個標(biāo)志,只要在這個堆上分配內(nèi)存,如果內(nèi)存不足都拋出異常。
如果分配成功HeapAlloc會返回內(nèi)存塊地址。否則將會返回NULL。
默認(rèn)情況下,對堆的訪問會依次進(jìn)行。當(dāng)任何程序試圖從堆中分配一塊內(nèi)存時,HeapAlloc會執(zhí)行以下操作:
1:遍歷已分配的內(nèi)存的鏈表和閑置內(nèi)存的鏈表。
2:找到一塊足夠大的閑置內(nèi)存塊。
3:分配一塊新的內(nèi)存,將2找到的內(nèi)存塊標(biāo)記為已分配。
4:將新分配的內(nèi)存塊添加到已分配的鏈表中。
注意:在分配大于1MB的內(nèi)存時應(yīng)該避免使用堆函數(shù),而應(yīng)該使用VirtualAlloc函數(shù)。
HeapReAlloc可以改變堆中某一塊內(nèi)存的大?。?/span>
[cpp] view plaincopy
hHeap用來標(biāo)識一個堆。
fdwFlags用來在調(diào)整內(nèi)存塊大小時用到這些標(biāo)志??梢杂幸韵聵?biāo)志:HEAP_GENERATE_EXCEPTIONS,HEAP_NO_SERIALIZE,HEAP_ZERO_MEMORY,HEAP_REALLOC_IN_PLACE_ONLY。
前兩個標(biāo)志與前面介紹的一樣。只有當(dāng)增大內(nèi)存塊時HEAP_ZERO_MEMORY才有用。額外的字節(jié)會被清0。
在增大內(nèi)存塊時HeapReAlloc可能會移動內(nèi)存塊,HEAP_REALLOC_IN_PLACE_ONLY標(biāo)志告訴HeapReAlloc盡量不要移動內(nèi)存塊。如果不移動不能增大內(nèi)存塊,則HeapReAlloc返回新地址。
pvMem指定要調(diào)整大小的內(nèi)存塊。
dwBytes指定內(nèi)存塊的新大小。
分配一塊內(nèi)存后,調(diào)用HeapSize可以獲得這塊內(nèi)存的實際大小:
[cpp] view plaincopy
hHeap用來標(biāo)識堆。
pvMem表示內(nèi)存地址。
dwFlags可以是0或HEAP_NO_SERIALIZE
當(dāng)不要使用一塊內(nèi)存時可以調(diào)用HeapFree來釋放它:
[cpp] view plaincopy
如果操作成功則返回TRUE。調(diào)用這個函數(shù)可能會使堆管理器撤銷一些已經(jīng)調(diào)撥的物理存儲器。
如果應(yīng)用程序不再需要自己創(chuàng)建的堆,可以調(diào)用HeapDestroy來銷毀它:
[cpp] view plaincopy
此時系統(tǒng)會收回堆所占用的物理存儲器和地址空間區(qū)域。執(zhí)行成功則返回TRUE。如果我們不調(diào)用此函數(shù)主動銷毀自己創(chuàng)建的堆,在進(jìn)程結(jié)束時,系統(tǒng)會替我們銷毀。我們不能調(diào)用此函數(shù)銷毀默認(rèn)堆,默認(rèn)堆由系統(tǒng)管理。
在C++中使用堆
在C++中我們可以調(diào)用new操作符來分配類對象。不需要時可以調(diào)用delete來釋放它。如
[cpp] view plaincopy
在編譯此段代碼時,編譯器會首先檢查類CA是否重載了new操作符成員函數(shù)。如果找到編譯器會調(diào)用這個函數(shù)。否額,會調(diào)用C++標(biāo)準(zhǔn)的new操作符。
[cpp] view plaincopy
對此句代碼C++編譯器會執(zhí)行與上面類似的步驟,只有CA類沒有重載delete操作符成員函數(shù)時,才會調(diào)用標(biāo)準(zhǔn)的C++delete運算符。
通過對C++類的new和delete操作符進(jìn)行重載,我們可以非常容易的將堆函數(shù)加以運用:
[cpp] view plaincopy
上述代碼調(diào)用operator new和operator delete是從默認(rèn)堆中分配的內(nèi)存。我們可以讓其在自己創(chuàng)建的堆中分配內(nèi)存,一般讓所有對象共享同一個堆,每個對象都創(chuàng)建一個堆為導(dǎo)致額外的性能開銷??梢圆捎糜嫈?shù)法來對堆的生存期進(jìn)行控制。
ToolHelp函數(shù)允許我們枚舉進(jìn)程的堆以及分配的內(nèi)存塊。它包括一下函數(shù):Heap32First,Heap32Next,Heap32ListFirst和Heap32ListNext。
由于進(jìn)程在自己的地址空間可以有多個堆,GetProcessHeaps可以讓我們得到這些堆的句柄。
[cpp] view plaincopy
phHeaps是一個數(shù)組指針。用以存儲返回的堆句柄。
dwNumHeaps是數(shù)組大小。
函數(shù)返回句柄數(shù)組個數(shù)。
函數(shù)所返回的句柄數(shù)組中也包括進(jìn)程的默認(rèn)堆的句柄。
[cpp] view plaincopy
HeapValidate可以驗證堆的完整性。
[cpp] view plaincopy
通常在調(diào)用這個函數(shù)時,我們會傳一個堆句柄和一個標(biāo)志0,并傳入NULL給pvMem。該函數(shù)會遍歷堆中的各個內(nèi)存塊,確保沒有任何一塊內(nèi)存被破壞。如果給pvMem制定一塊內(nèi)存地址,那么函數(shù)就只檢查這一塊內(nèi)存的完整性。
為了讓堆中閑置的內(nèi)存塊能重新結(jié)合在一起,并撤銷調(diào)撥給堆中閑置內(nèi)存塊的存儲器,可以調(diào)用HeapCompact:
[cpp] view plaincopy
一般來說會傳0給fdwFlags。
下面兩個函數(shù)要配對使用,用于線程同步: [cpp] view plaincopy
當(dāng)?shù)谝粋€線程調(diào)用HeapLock時,它就占有了堆。其他線程在調(diào)用堆函數(shù)時,系統(tǒng)就會暫停其他線程。只有當(dāng)?shù)谝粋€線程調(diào)用HeapUnlock之后才會喚醒被暫停的進(jìn)程。
HeapAlloc,HeapSize,HeapFree這些函數(shù)會在內(nèi)部調(diào)用HeapLock和HeapUnlock,一般來說不需要自己去調(diào)用HeapLock和HeapUnlock。
最后一個函數(shù)是HeapWalk,它允許我們遍歷堆的內(nèi)容。只用于調(diào)試。具體不再介紹。 以上參考自《Windows核心編程》第五版第三部分,如有紕漏,請不吝指正??! |
|