對內(nèi)存進(jìn)行操作的第三個機(jī)制是使用堆棧。堆棧可以用來分配許多較小的數(shù)據(jù)塊。例如,若要對鏈接表和鏈接樹進(jìn)行管理,最好的方法是使用堆棧,堆棧的優(yōu)點(diǎn)是,可以不考慮分配粒度和頁面邊界之類的問題,集中精力處理手頭的任務(wù)。堆棧的缺點(diǎn)是,分配和釋放內(nèi)存塊的速度比其他機(jī)制要慢,并且無法直接控制物理存儲器的提交和回收。
從內(nèi)部來講,堆棧是保留的地址空間的一個區(qū)域。開始時(shí),保留區(qū)域中的大多數(shù)頁面沒有被提交物理存儲器。當(dāng)從堆棧中進(jìn)行越來越多的內(nèi)存分配時(shí),堆棧管理器將把更多的物理存儲器提交給堆棧。物理存儲器總是從系統(tǒng)的頁文件中分配的,當(dāng)釋放堆棧中的內(nèi)存塊時(shí),堆棧管理器將收回這些物理存儲器。
線程的堆棧:
每當(dāng)創(chuàng)建一個線程時(shí),系統(tǒng)就會為線程的堆棧(每個線程有它自己的堆棧)保留一個堆棧空間區(qū)域,并將一些物理存儲器提交給這個已保留的區(qū)域。按照默認(rèn)設(shè)置,系統(tǒng)保留1 MB的地址空間并提交兩個頁面的內(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)建一個線程的堆棧時(shí),系統(tǒng)將會保留一個鏈接程序的/ S TA C K開關(guān)指明的地址空間區(qū)域。
進(jìn)程的默認(rèn)堆棧
當(dāng)進(jìn)程初始化時(shí),系統(tǒng)在進(jìn)程的地址空間中創(chuàng)建一個堆棧。該堆棧稱為進(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鏈接開關(guān),改變堆棧的1 M B默認(rèn)區(qū)域大小。由于D L L沒有與其相關(guān)的堆棧,所以當(dāng)鏈接D L L時(shí),不應(yīng)該使用/ H E A P鏈接開關(guān)。/ H E A P鏈接開關(guān)的句法如下:
許多Wi n d o w s函數(shù)要求進(jìn)程使用其默認(rèn)堆棧。特別是widows提供的API。對默認(rèn)堆棧的訪問是順序進(jìn)行的。換句話說,系統(tǒng)必須保證在規(guī)定的時(shí)間內(nèi),每次只有一個線程能夠分配和釋放默認(rèn)堆棧中的內(nèi)存塊。如果兩個線程試圖同時(shí)分配默認(rèn)堆棧中的內(nèi)存塊,那么只有一個線程能夠分配內(nèi)存塊,另一個線程必須等待第一個線程的內(nèi)存塊分配之后,才能分配它的內(nèi)存塊。一旦第一個線程的內(nèi)存塊分配完,堆棧函數(shù)將允許第二個線程分配內(nèi)存塊。這種順序訪問方法對速度有一定的影響。如果你的應(yīng)用程序只有一個線程,并且你想要以最快的速度訪問堆棧,那么應(yīng)該創(chuàng)建你自己的獨(dú)立的堆棧,不要使用進(jìn)程的默認(rèn)堆棧。
單個進(jìn)程可以同時(shí)擁有若干個堆棧。這些堆棧可以在進(jìn)程的壽命期中創(chuàng)建和撤消。但是,默認(rèn)堆棧是在進(jìn)程開始執(zhí)行之前創(chuàng)建的,并且在進(jìn)程終止運(yùn)行時(shí)自動被撤消。不能撤消進(jìn)程的默認(rèn)堆棧。每個堆棧均用它自己的堆棧句柄來標(biāo)識,用于分配和釋放堆棧中的內(nèi)存塊的所有堆棧函數(shù)都需要這個堆棧句柄作為其參數(shù)。
可以通過調(diào)用G e t P r o c e s s H e a p函數(shù)獲取你的進(jìn)程默認(rèn)堆棧的句柄:
為什么要創(chuàng)建輔助堆棧
除了進(jìn)程的默認(rèn)堆棧外,可以在進(jìn)程的地址空間中創(chuàng)建一些輔助堆棧。由于下列原因,你可能想要在自己的應(yīng)用程序中創(chuàng)建一些輔助堆棧:
? 保護(hù)組件。
? 更加有效地進(jìn)行內(nèi)存管理。
? 進(jìn)行本地訪問。
? 減少線程同步的開銷。
? 迅速釋放。
保護(hù)組件
通過創(chuàng)建多個獨(dú)立的堆棧,是數(shù)據(jù)隔離,且相互獨(dú)立的操作。
更有效的內(nèi)存管理
通過在堆棧中分配同樣大小的對象,就可以更加有效地管理堆棧。就是把大小相同的對象放在一個堆棧中進(jìn)行分配。
進(jìn)行本地訪問
每當(dāng)系統(tǒng)必須在R A M與系統(tǒng)的頁文件之間進(jìn)行R A M頁面的交換時(shí),系統(tǒng)的運(yùn)行性能就會受到很大的影響。如果經(jīng)常訪問局限于一個小范圍地址的內(nèi)存,那么系統(tǒng)就不太可能需要在R A M與磁盤之間進(jìn)行頁面的交換。
所以,在設(shè)計(jì)應(yīng)用程序的時(shí)候,如果有些數(shù)據(jù)將被同時(shí)訪問,那么最好把它們分配在互相靠近的位置上。
減少線程同步的開銷
正如下面就要介紹的那樣,按照默認(rèn)設(shè)置,堆棧是順序運(yùn)行的,這樣,如果多個線程試圖同時(shí)訪問堆棧,就不會使數(shù)據(jù)受到破壞。但是,堆棧函數(shù)必須執(zhí)行額外的代碼,以保證堆棧對線程的安全性。如果要進(jìn)行大量的堆棧分配操作,那么執(zhí)行這些額外的代碼會增加很大的負(fù)擔(dān),從而降低你的應(yīng)用程序的運(yùn)行性能。當(dāng)你創(chuàng)建一個新堆棧時(shí),可以告訴系統(tǒng),只有一個線程將訪問該堆棧,因此額外的代碼將不執(zhí)行。(就是用多個堆棧來減少同步的性能消耗)
迅速釋放堆棧
最后要說明的是,將專用堆棧用于某些數(shù)據(jù)結(jié)構(gòu)后,就可以釋放整個堆棧,而不必顯式釋放堆棧中的每個內(nèi)存塊。例如,當(dāng)Windows Explorer遍歷硬盤驅(qū)動器的目錄層次結(jié)構(gòu)時(shí),它必須在內(nèi)存中建立一個樹狀結(jié)構(gòu)。如果你告訴Windows Explorer刷新它的顯示器,它只需要撤消包含這個樹狀結(jié)構(gòu)的堆棧并且重新運(yùn)行即可(當(dāng)然,假定它將專用堆棧用于存放目錄樹信息)。對于許多應(yīng)用程序來說,這是非常方便的,并且它們也能更快地運(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)試圖從堆棧分配一個內(nèi)存塊時(shí), H e a p A l l o c函數(shù)(下面將要介紹)必須執(zhí)行下列操作:
1) 遍歷分配的和釋放的內(nèi)存塊的鏈接表。
2) 尋找一個空閑內(nèi)存塊的地址。
3) 通過將空閑內(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)用程序開始時(shí)分配的內(nèi)存塊比較大,然后,當(dāng)所有數(shù)據(jù)放入內(nèi)存塊后,再縮小內(nèi)存塊的大小。有些應(yīng)用程序開始時(shí)分配的內(nè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ù)來檢索內(nèi)存塊的實(shí)際大小:
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)建的堆棧,可以通過調(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)存塊,也可以將堆棧占用的物理存儲器和保留的地址空間區(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)行之前沒有顯式撤消堆棧,那么系統(tǒng)將為你將它撤消。但是,只有當(dāng)進(jìn)程終止運(yùn)行時(shí),堆棧才能被撤消。如果線程創(chuàng)建了一個堆棧,當(dāng)線程終止運(yùn)行時(shí),該堆棧將不會被撤消。
在進(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)將忽略對該函數(shù)的調(diào)用。
由于進(jìn)程的地址空間中可以存在多個堆棧,因此可以使用G e t P r o c e s s H e a p s函數(shù)來獲取現(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ù),必須首先分配一個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í),通常要傳遞一個堆棧句柄,一個值為0的標(biāo)志(唯一的另一個合法標(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)存塊都完好無損。為了使該函數(shù)運(yùn)行得更快,可以為參數(shù)p v M e m傳遞一個特定的內(nèi)存塊的地址。這樣做可使該函數(shù)只檢查單個內(nèi)存塊的有效性。
若要合并地址中的空閑內(nèi)存塊并收回不包含已經(jīng)分配的地址內(nèi)存塊的存儲器頁面,可以調(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。
下面兩個函數(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ù)解鎖之前不允許它醒來。
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ù)來確保對堆棧的訪問能夠順序進(jìn)行。自己調(diào)用H e a p L o c k或H e a p U n l o c k這種情況是不常見的。
最后一個堆棧函數(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