《Windows核心编程》学习笔记(14)– 堆

时间:2022-12-15 07:26:23

堆非常适合分配大量的小型数据。它是用来管理链表和树的最佳方式。但是它分配和释放内存块的速度比虚拟内存和内存映射文件要慢,而且也无法再对物理存储器的调拨和撤销调拨进行直接控制。

一个进程同时可以有多个堆,进程在整个生命周期内可以创建和销毁这些堆。但是, 默认的堆是在进程开始运行之前由系统自动创建的,在进程终止后会自动销毁。我们无法销毁进程的默认堆。每个堆都有一个用来标识自己的堆句柄,所有分配和释 放内存块的堆函数都会在参数中用到这个堆句柄。

 

我们可以调用如下函数来得到进程默认堆得句柄:

HANDLE WINAPI GetProcessHeap(void);

 

创建额外的堆

HANDLE WINAPI HeapCreate(
  __in          DWORD flOptions,
  __in          SIZE_T dwInitialSize,
  __in          SIZE_T dwMaximumSize
);

flOptions参数用来表示对堆的操作该如何进行。可以指定0 HEAP_NO_SERIALIZE HEAP_GENERATE_EXCEPTIONS HEAP_CREATE_ENABLE_EXECUTE

HEAP_NO_SERIALIZE标志使得多个线程可以同时访问一个堆,这使得堆中的数据可能会遭到破坏,因此应该避免使用。

HEAP_GENERATE_EXCEPTIONS标志告诉系统,每当在堆中分配或者重新分配内存块失败的时候,抛出一个异常。

HEAP_CREATE_ENABLE_EXECUTE标志告诉系统,我们想在堆中存放可执行代码。如果不设置这个标志,那么当我们试图在来自堆的内存块中执行代码时,系统会抛出EXCEPTION_ACCESS_VIOLATION异常。

 

dwInitialSize参数表示一开始要调拨给堆的字节数。如果需要,HeapCreate会把这个值向上取整到CPU页面大小的整数倍。

 

dwMaximumSize参数表示堆能增长到的最大大小。如果dwMaximumSize 大于0,那么创建的堆会有一个最大大小。

如果dwMaximumSize0,那么创建的堆将是可增长的,直到用尽所有的物理存储器为止。

 

 

从堆中分配内存块

LPVOID WINAPI HeapAlloc(
  __in          HANDLE hHeap,
  __in          DWORD dwFlags,
  __in          SIZE_T dwBytes
);

 

调整内存块的大小

LPVOID WINAPI HeapReAlloc(
  __in          HANDLE hHeap,
  __in          DWORD dwFlags,
  __in          LPVOID lpMem,
  __in          SIZE_T dwBytes
);

dwFlags参数可以是HEAP_GENERATE_EXCEPTIONSHEAP_NO_SERIALIZEHEAP_ZERO_MEMORY HEAP_REALLOC_IN_PLACE_ONLY

HEAP_ZERO_MEMORY这个标志只有在增大内存块的大小时才有用。在这种情况下,内存中的额外字节会被清零。减小内存块的大小时,这个标志不起任何作用。

HEAP_REALLOC_IN_PLACE_ONLY在增大内存块的时候,HeapReAlloc可能会在堆内部移动内存块,而这个标志用来告诉HeapReAlloc不要移动内存块。

如果HeapReAlloc函数能在不移动内存块的前提下就能让它增大,那么函数会返回原来的内存块地址。另一方面,如果HeapReAlloc必须移动内存块的地址,那么函数将返回一个指向一块更大内存块的新地址。

如果一个内存块是链表或者树的一部分,那么需要指定这个标志。因为链表或者树的其他节点可能包含指向当前节点的指针,把节点移动到堆中其他的地方会破坏链表或树的完整性。

 

lpMemdwBytes参数分别用来指定想要调整大小的内存块的原始地址以及内存块的新大小,以字节为单位。

 

获得内存块的大小

分配一块内存后,可以调用如下函数来得到这块内存的实际大小:

SIZE_T WINAPI HeapSize(
  __in          HANDLE hHeap,
  __in          DWORD dwFlags,
  __in          LPCVOID lpMem
);

参数hHeap用来标识堆。

参数dwFlags可以是0或者HEAP_NO_SERIALIZE

参数lpMem标识内存块快的地址。

 

释放内存块

BOOL WINAPI HeapFree(
  __in          HANDLE hHeap,
  __in          DWORD dwFlags,
  __in          LPVOID lpMem
);

 

销毁堆

BOOL WINAPI HeapDestroy(
  __in          HANDLE hHeap
);

调用这个函数的时候,系统会释放堆中包含的所有内存块,同时系统会收回堆所占用的物理存储器和地址空间区域。

 

其他堆函数

1.ToolHelp函数允许我们枚举进程中的堆以及堆中分配的内存块。

Heap32FirstHeap32Next Heap32ListFirst Heap32ListNext

 

2.得到进程中所有堆的句柄

DWORD WINAPI GetProcessHeaps(
  __in          DWORD NumberOfHeaps,
  __out         PHANDLE ProcessHeaps
);

用法如下:

HANDLE hHeaps[25];

DWORD dwHeaps = GetProcessHeaps(25, hHeaps);

If(dwHeaps > 25)

{

         //大于25个句柄

}

else

{

         //hHeaps[0]

}

注意:函数所返回的句柄数组中也包括了进程的默认堆句柄。

 

3.验证堆的完整性

BOOL WINAPI HeapValidate(
  __in          HANDLE hHeap,
  __in          DWORD dwFlags,
  __in          LPCVOID lpMem
);

通常调用这个函数的时候, 我们会传入一个堆句柄,和一个标志0,并传NULLlpMem

该函数会遍历堆中的各个内存块,确保没有任何一块内存被破坏。

 

4.为了让堆中闲置的内存块能重新接合在一起,并撤销调拨给堆中闲置内存块的存储器,可以调用如下函数:

SIZE_T WINAPI HeapCompact(
  __in          HANDLE hHeap,
  __in          DWORD dwFlags
);

一般来说,我们会传0 dwFlags参数。