VirtualAlloc 分配的內存是以 4K 為最小單位、連續的內存地址(但映射到真實的內存時它不一定是連續的), 前面說了, 它不適合分配小內存(譬如只有幾個字節的變量); 局部的變量在 "棧" 中有程序自動管理, 那麼那些全局的小變量怎麼辦呢? 這就要用到 "堆".
這樣看來, VirtualAlloc 分配的內存既不是 "棧" 也不是 "堆"; VirtualAlloc 分配的內存地址是連續的, "堆" 中內容一般是不連續的, 所以管理 "堆" 比較麻煩, 它是通過雙線鏈表的結構方式管理的; 程序可以擁有若干個 "堆", 每一個 "堆" 都會有一個句柄, 訪問 "堆" 中的內容時先要找到這個 "堆", 然後再遍歷鏈表, 這可能就是 "堆" 比 "棧" 慢的根本原因.
在 "堆" 中分配內存(HeapAlloc)前先要建立 "堆"(HeapCreate), 就像程序有默認的 "棧" 一樣, 每一個程序都有一個默認建立的 "堆"(可以用 GetProcessHeap 獲取這個 "默認堆" 的句柄), 我們在 Delphi 中用到 "堆" 時, 使用的就是這個 "默認堆". 如果讓程序更靈活地擁有多個 "堆", 必須要用到 API 函數.
建立 "堆" 時會同時提交真實內存的, 這在申請大內存時會很慢, 所以默認堆也只有 1M, 但 "默認堆" 並沒有限制大小, 它會根據需要動態增長.
有了 "默認堆" 還有必要申請其他的 "堆" 嗎? 這只有在多線程中才能體現出來, 和 "棧" 不一樣, 程序會給每個線程分配一個 "棧區"; 而 "默認堆" 是進程中的所有線程公用的, 當一個線程使用 "默認堆" 時, 另一個需要使用 "堆" 的線程就要先掛起等待, 也就是它們不能同時使用; 只有通過 API 函數重新建立的私有堆才是互不干涉、最有效率的.
先了解一下 "堆" 相關的函數.//建立堆; 注意建立時指定的尺寸也是按頁大小(PageSize)對齊的, 譬如指定 15k, 實際會分配 16K.
舉例放下篇吧.
HeapCreate(
flOptions: DWord; {堆屬性選項, 見下表}
dwInitialSize: DWord; {初始尺寸, 單位是字節; 該大小會被直接提交到實際的內存}
dwMaximumSize: DWord {最大尺寸, 如果不限定最大值就設為 0}
): THandle; {返回堆句柄; 失敗返回 0, 但如果參數 flOptions 允許了異常, 失敗會返回異常標識}
//flOptions 參數可選值:
HEAP_NO_SERIALIZE = 1; {非互斥, 此標記可允許多個線程同時訪問此堆}
HEAP_GENERATE_EXCEPTIONS = 4; {當建立堆出錯時, 此標記可激發一個異常並返回異常標識}
HEAP_ZERO_MEMORY = 8; {把分配的內存初始化為 0}
//flOptions 參數指定有 HEAP_GENERATE_EXCEPTIONS 時, 可能返回的異常:
STATUS_Access_VIOLATION = DWord($C0000005); {參數錯誤}
STATUS_NO_MEMORY = DWord($C0000017); {內存不足}
//銷毀堆
HeapDestroy(
hHeap: THandle {堆句柄}
): BOOL; {}
//從堆中申請內存
HeapAlloc(
hHeap: THandle; {堆句柄}
dwFlags: DWord; {內存屬性選項, 見下表}
dwBytes: DWord {申請內存的大小, 單位是字節}
): Pointer; {返回內存指針; 失敗返回 0 或異常, 情況和建立堆是一樣}
//dwFlags 參數可選值:
HEAP_NO_SERIALIZE = 1; {非互斥, 此標記可允許多個線程同時訪問此堆}
HEAP_GENERATE_EXCEPTIONS = 4; {當建立堆出錯時, 此標記可激發一個異常並返回異常標識}
HEAP_ZERO_MEMORY = 8; {把分配的內存初始化為 0}
{能看出這和堆的屬性選項是一樣的; 如果 dwFlags 參數設為 0, 將使用堆的屬性; 如果重新指定將覆蓋堆的屬性}
{另外: 如果堆是默認堆, 也就是堆句柄來自 GetProcessHeap, dwFlags 參數會被忽略}
//改變堆內存的大小, 也就是重新分配
HeapReAlloc(
hHeap: THandle; {句柄}
dwFlags: DWord; {內存屬性選項; 該參數比 HeapAlloc 多出一個選項, 見下表}
lpMem: Pointer; {原內存指針}
dwBytes: DWord {新的尺寸}
): Pointer; {同 HeapAlloc}
//dwFlags 參數可選值:
HEAP_NO_SERIALIZE = 1; {非互斥, 此標記可允許多個線程同時訪問此堆}
HEAP_GENERATE_EXCEPTIONS = 4; {當建立堆出錯時, 此標記可激發一個異常並返回異常標識}
HEAP_ZERO_MEMORY = 8; {把分配的內存初始化為 0}
HEAP_REALLOC_IN_PLACE_ONLY = 16; {此標記不允許改變原來的內存位置}
//獲取堆中某塊內存的大小
HeapSize(
hHeap: THandle; {堆句柄}
dwFlags: DWord; {內存屬性; 可選值是 0 或 HEAP_NO_SERIALIZE, 後者可確保同步訪問}
lpMem: Pointer {內存指針}
): DWord; {成功返回字節為單位的大小; 失敗返回 $FFFFFFFF}
//釋放堆中指定的內存塊
HeapFree(
hHeap: THandle; {堆句柄}
dwFlags: DWord; {內存屬性; 可選值是 0 或 HEAP_NO_SERIALIZE}
lpMem: Pointer {內存指針}
): BOOL; {}
//驗證堆
HeapValidate(
hHeap: THandle; {}
dwFlags: DWord; {}
lpMem: Pointer {}
): BOOL; {}
//整理堆
HeapCompact(
hHeap: THandle; {}
dwFlags: DWord {}
): UINT; {}
//鎖定堆
HeapLock(
hHeap: THandle {}
): BOOL; {}
//鎖定後的解鎖
HeapUnlock(
hHeap: THandle {}
): BOOL; {}
//列舉堆中的內存塊
HeapWalk(
hHeap: THandle; {}
var lpEntry: TProcessHeapEntry {}
): BOOL; {}