對(duì)內(nèi)存進(jìn)行操作的第三個(gè)機(jī)制是使用堆棧。堆??梢杂脕?lái)分配許多較小的數(shù)據(jù)塊。例如,若要對(duì)鏈接表和鏈接樹(shù)進(jìn)行管理,最好的方法是使用堆棧,堆棧的優(yōu)點(diǎn)是,可以不考慮分配粒度和頁(yè)面邊界之類的問(wèn)題,集中精力處理手頭的任務(wù)。堆棧的缺點(diǎn)是,分配和釋放內(nèi)存塊的速度比其他機(jī)制要慢,并且無(wú)法直接控制物理存儲(chǔ)器的提交和回收。
從內(nèi)部來(lái)講,堆棧是保留的地址空間的一個(gè)區(qū)域。開(kāi)始時(shí),保留區(qū)域中的大多數(shù)頁(yè)面沒(méi)有被提交物理存儲(chǔ)器。當(dāng)從堆棧中進(jìn)行越來(lái)越多的內(nèi)存分配時(shí),堆棧管理器將把更多的物理存儲(chǔ)器提交給堆棧。物理存儲(chǔ)器總是從系統(tǒng)的頁(yè)文件中分配的,當(dāng)釋放堆棧中的內(nèi)存塊時(shí),堆棧管理器將收回這些物理存儲(chǔ)器。
線程的堆棧:
每當(dāng)創(chuàng)建一個(gè)線程時(shí),系統(tǒng)就會(huì)為線程的堆棧(每個(gè)線程有它自己的堆棧)保留一個(gè)堆棧空間區(qū)域,并將一些物理存儲(chǔ)器提交給這個(gè)已保留的區(qū)域。按照默認(rèn)設(shè)置,系統(tǒng)保留1 MB的地址空間并提交兩個(gè)頁(yè)面的內(nèi)存。但是,這些默認(rèn)值是可以修改的,方法是在你鏈接應(yīng)用程序時(shí)設(shè)定M i c r o s o f t的鏈接程序的/ S TA C K選項(xiàng):
當(dāng)創(chuàng)建一個(gè)線程的堆棧時(shí),系統(tǒng)將會(huì)保留一個(gè)鏈接程序的/ S TA C K開(kāi)關(guān)指明的地址空間區(qū)域。
進(jìn)程的默認(rèn)堆棧
當(dāng)進(jìn)程初始化時(shí),系統(tǒng)在進(jìn)程的地址空間中創(chuàng)建一個(gè)堆棧。該堆棧稱為進(jìn)程的默認(rèn)堆棧。按照默認(rèn)設(shè)置,該堆棧的地址空間區(qū)域的大小是1 MB。但是,系統(tǒng)可以擴(kuò)大進(jìn)程的默認(rèn)堆棧,使它大于其默認(rèn)值。當(dāng)創(chuàng)建應(yīng)用程序時(shí),可以使用/ H E A P鏈接開(kāi)關(guān),改變堆棧的1 M B默認(rèn)區(qū)域大小。由于D L L沒(méi)有與其相關(guān)的堆棧,所以當(dāng)鏈接D L L時(shí),不應(yīng)該使用/ H E A P鏈接開(kāi)關(guān)。/ H E A P鏈接開(kāi)關(guān)的句法如下:
許多Wi n d o w s函數(shù)要求進(jìn)程使用其默認(rèn)堆棧。特別是widows提供的API。對(duì)默認(rèn)堆棧的訪問(wèn)是順序進(jìn)行的。換句話說(shuō),系統(tǒng)必須保證在規(guī)定的時(shí)間內(nèi),每次只有一個(gè)線程能夠分配和釋放默認(rèn)堆棧中的內(nèi)存塊。如果兩個(gè)線程試圖同時(shí)分配默認(rèn)堆棧中的內(nèi)存塊,那么只有一個(gè)線程能夠分配內(nèi)存塊,另一個(gè)線程必須等待第一個(gè)線程的內(nèi)存塊分配之后,才能分配它的內(nèi)存塊。一旦第一個(gè)線程的內(nèi)存塊分配完,堆棧函數(shù)將允許第二個(gè)線程分配內(nèi)存塊。這種順序訪問(wèn)方法對(duì)速度有一定的影響。如果你的應(yīng)用程序只有一個(gè)線程,并且你想要以最快的速度訪問(wèn)堆棧,那么應(yīng)該創(chuàng)建你自己的獨(dú)立的堆棧,不要使用進(jìn)程的默認(rèn)堆棧。
單個(gè)進(jìn)程可以同時(shí)擁有若干個(gè)堆棧。這些堆??梢栽谶M(jìn)程的壽命期中創(chuàng)建和撤消。但是,默認(rèn)堆棧是在進(jìn)程開(kāi)始執(zhí)行之前創(chuàng)建的,并且在進(jìn)程終止運(yùn)行時(shí)自動(dòng)被撤消。不能撤消進(jìn)程的默認(rèn)堆棧。每個(gè)堆棧均用它自己的堆棧句柄來(lái)標(biāo)識(shí),用于分配和釋放堆棧中的內(nèi)存塊的所有堆棧函數(shù)都需要這個(gè)堆棧句柄作為其參數(shù)。
可以通過(guò)調(diào)用G e t P r o c e s s H e a p函數(shù)獲取你的進(jìn)程默認(rèn)堆棧的句柄:
為什么要?jiǎng)?chuàng)建輔助堆棧
除了進(jìn)程的默認(rèn)堆棧外,可以在進(jìn)程的地址空間中創(chuàng)建一些輔助堆棧。由于下列原因,你可能想要在自己的應(yīng)用程序中創(chuàng)建一些輔助堆棧:
? 保護(hù)組件。
? 更加有效地進(jìn)行內(nèi)存管理。
? 進(jìn)行本地訪問(wèn)。
? 減少線程同步的開(kāi)銷。
? 迅速釋放。
保護(hù)組件
通過(guò)創(chuàng)建多個(gè)獨(dú)立的堆棧,是數(shù)據(jù)隔離,且相互獨(dú)立的操作。
更有效的內(nèi)存管理
通過(guò)在堆棧中分配同樣大小的對(duì)象,就可以更加有效地管理堆棧。就是把大小相同的對(duì)象放在一個(gè)堆棧中進(jìn)行分配。
進(jìn)行本地訪問(wèn)
每當(dāng)系統(tǒng)必須在R A M與系統(tǒng)的頁(yè)文件之間進(jìn)行R A M頁(yè)面的交換時(shí),系統(tǒng)的運(yùn)行性能就會(huì)受到很大的影響。如果經(jīng)常訪問(wèn)局限于一個(gè)小范圍地址的內(nèi)存,那么系統(tǒng)就不太可能需要在R A M與磁盤之間進(jìn)行頁(yè)面的交換。
所以,在設(shè)計(jì)應(yīng)用程序的時(shí)候,如果有些數(shù)據(jù)將被同時(shí)訪問(wèn),那么最好把它們分配在互相靠近的位置上。
減少線程同步的開(kāi)銷
正如下面就要介紹的那樣,按照默認(rèn)設(shè)置,堆棧是順序運(yùn)行的,這樣,如果多個(gè)線程試圖同時(shí)訪問(wèn)堆棧,就不會(huì)使數(shù)據(jù)受到破壞。但是,堆棧函數(shù)必須執(zhí)行額外的代碼,以保證堆棧對(duì)線程的安全性。如果要進(jìn)行大量的堆棧分配操作,那么執(zhí)行這些額外的代碼會(huì)增加很大的負(fù)擔(dān),從而降低你的應(yīng)用程序的運(yùn)行性能。當(dāng)你創(chuàng)建一個(gè)新堆棧時(shí),可以告訴系統(tǒng),只有一個(gè)線程將訪問(wèn)該堆棧,因此額外的代碼將不執(zhí)行。(就是用多個(gè)堆棧來(lái)減少同步的性能消耗)
迅速釋放堆棧
最后要說(shuō)明的是,將專用堆棧用于某些數(shù)據(jù)結(jié)構(gòu)后,就可以釋放整個(gè)堆棧,而不必顯式釋放堆棧中的每個(gè)內(nèi)存塊。例如,當(dāng)Windows Explorer遍歷硬盤驅(qū)動(dòng)器的目錄層次結(jié)構(gòu)時(shí),它必須在內(nèi)存中建立一個(gè)樹(shù)狀結(jié)構(gòu)。如果你告訴Windows Explorer刷新它的顯示器,它只需要撤消包含這個(gè)樹(shù)狀結(jié)構(gòu)的堆棧并且重新運(yùn)行即可(當(dāng)然,假定它將專用堆棧用于存放目錄樹(shù)信息)。對(duì)于許多應(yīng)用程序來(lái)說(shuō),這是非常方便的,并且它們也能更快地運(yùn)行。
如何創(chuàng)建輔助堆棧
你可以在進(jìn)程中創(chuàng)建輔助堆棧,方法是讓線程調(diào)用H e a p C r e a t e函數(shù):
HANDLE HeapCreate(
DWORD fdwOptions,
SIZE_T dwInitialSize,
SIZE_T dwMaximumSize);
當(dāng)試圖從堆棧分配一個(gè)內(nèi)存塊時(shí), H e a p A l l o c函數(shù)(下面將要介紹)必須執(zhí)行下列操作:
1) 遍歷分配的和釋放的內(nèi)存塊的鏈接表。
2) 尋找一個(gè)空閑內(nèi)存塊的地址。
3) 通過(guò)將空閑內(nèi)存塊標(biāo)記為“已分配”分配新內(nèi)存塊。
4) 將新內(nèi)存塊添加給內(nèi)存塊鏈接表。
從堆棧中分配內(nèi)存塊
若要從堆棧中分配內(nèi)存塊,只需要調(diào)用H e a p A l l o c函數(shù):
PVOID HeapAlloc(
HANDLE hHeap,
DWORD fdwFlags,
SIZE_T dwBytes);
改變內(nèi)存塊的大小
常常需要改變內(nèi)存塊的大小。有些應(yīng)用程序開(kāi)始時(shí)分配的內(nèi)存塊比較大,然后,當(dāng)所有數(shù)據(jù)放入內(nèi)存塊后,再縮小內(nèi)存塊的大小。有些應(yīng)用程序開(kāi)始時(shí)分配的內(nèi)存塊比較小,后來(lái)需要將更多的數(shù)據(jù)拷貝到內(nèi)存塊中去時(shí),再設(shè)法擴(kuò)大它的大小。如果要改變內(nèi)存塊的大小,可以調(diào)用H e a p R e A l l o c函數(shù):
PVOID HeapReAlloc(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem,
SIZE_T dwBytes);
了解內(nèi)存塊的大小
當(dāng)內(nèi)存塊分配后,可以調(diào)用H e a p S i z e函數(shù)來(lái)檢索內(nèi)存塊的實(shí)際大?。?/font>
SIZE_T HeapSize(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
釋放內(nèi)存塊
當(dāng)不再需要內(nèi)存塊時(shí),可以調(diào)用H e a p F r e e函數(shù)將它釋放:
BOOL HeapFree(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem);
撤消堆棧
如果應(yīng)用程序不再需要它創(chuàng)建的堆棧,可以通過(guò)調(diào)用H e a p D e s t r o y函數(shù)將它撤消:
BOOL HeapDestroy(HANDLE hHeap);
調(diào)用H e a p D e s t r o y函數(shù)可以釋放堆棧中包含的所有內(nèi)存塊,也可以將堆棧占用的物理存儲(chǔ)器和保留的地址空間區(qū)域重新返回給系統(tǒng)。如果該函數(shù)運(yùn)行成功, H e a p D e s t r o y返回T R U E。如果在進(jìn)程終止運(yùn)行之前沒(méi)有顯式撤消堆棧,那么系統(tǒng)將為你將它撤消。但是,只有當(dāng)進(jìn)程終止運(yùn)行時(shí),堆棧才能被撤消。如果線程創(chuàng)建了一個(gè)堆棧,當(dāng)線程終止運(yùn)行時(shí),該堆棧將不會(huì)被撤消。
在進(jìn)程完全終止運(yùn)行之前,系統(tǒng)不允許進(jìn)程的默認(rèn)堆棧被撤消。如果將進(jìn)程的默認(rèn)堆棧的句柄傳遞給H e a p D e s t r o y函數(shù),系統(tǒng)將忽略對(duì)該函數(shù)的調(diào)用。
由于進(jìn)程的地址空間中可以存在多個(gè)堆棧,因此可以使用G e t P r o c e s s H e a p s函數(shù)來(lái)獲取現(xiàn)有堆棧的句柄:
DWORD GetProcessHeaps(
DWORD dwNumHeaps,
PHANDLE pHeaps);
若要調(diào)用G e t P r o c e s s H e a p s函數(shù),必須首先分配一個(gè)H A N D L E數(shù)組,然后調(diào)用下面的函數(shù):
HANDLE hHeaps[25];
DWORD dwHeaps = GetProcessHeaps(25, hHeaps);
if(dwHeaps > 5)
{
//More heaps are in this process than we expected.
}
else
{
//hHeaps[0] through hHeap[dwHeaps - 1]
//identify the existing heaps.
}
注意,當(dāng)該函數(shù)返回時(shí),你的進(jìn)程的默認(rèn)堆棧的句柄也包含在堆棧句柄的數(shù)組中。
H e a p Va l i d a t e函數(shù)用于驗(yàn)證堆棧的完整性:
BOOL HeapValidate(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
調(diào)用該函數(shù)時(shí),通常要傳遞一個(gè)堆棧句柄,一個(gè)值為0的標(biāo)志(唯一的另一個(gè)合法標(biāo)志是H E A P _ N O _ S E R I A L I Z E),并且為p v M e m傳遞N U L L。然后,該函數(shù)將遍歷堆棧中的內(nèi)存塊以確保所有內(nèi)存塊都完好無(wú)損。為了使該函數(shù)運(yùn)行得更快,可以為參數(shù)p v M e m傳遞一個(gè)特定的內(nèi)存塊的地址。這樣做可使該函數(shù)只檢查單個(gè)內(nèi)存塊的有效性。
若要合并地址中的空閑內(nèi)存塊并收回不包含已經(jīng)分配的地址內(nèi)存塊的存儲(chǔ)器頁(yè)面,可以調(diào)用下面的函數(shù):
UINT HeapCompact(
HANDLE hHeap,
DWORD fdwFlags);
通常情況下,可以為參數(shù)f d w F l a g s傳遞0,但是也可以傳遞H E A P _ N O _ S E R I A L I Z E。
下面兩個(gè)函數(shù)H e a p L o c k和H e a p U n l o c k是結(jié)合在一起使用的:
BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnlock(HANDLE hHeap);
這些函數(shù)是用于線程同步的。當(dāng)調(diào)用H e a p L o c k函數(shù)時(shí),調(diào)用線程將成為特定堆棧的所有者。如果其他任何線程調(diào)用堆棧函數(shù)(設(shè)定相同的堆棧句柄),系統(tǒng)將暫停調(diào)用線程的運(yùn)行,并且在堆棧被H e a p U n l o c k函數(shù)解鎖之前不允許它醒來(lái)。
H e a p A l l o c、H e a p S i z e和H e a p F r e e等函數(shù)在內(nèi)部調(diào)用H e a p L o c k和H e a p U n l o c k函數(shù)來(lái)確保對(duì)堆棧的訪問(wèn)能夠順序進(jìn)行。自己調(diào)用H e a p L o c k或H e a p U n l o c k這種情況是不常見(jiàn)的。
最后一個(gè)堆棧函數(shù)是H e a p Wa l k:
BOOL HeapWalk(
HANDLE hHeap,
PPROCESS_HEAP_ENTRY pHeapEntry);
該函數(shù)只用于調(diào)試目的。它使你能夠遍歷堆棧的內(nèi)容??梢远啻握{(diào)用該函數(shù)。
zz