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