1.堆
2.创建其它堆的原因
把链表和树分别放在不同的堆中就可以保证链表的写溢出操作不会影响树中的数据。
本地存储: 把可能被同时访问的数据连续放在一起(同一页中),减少换页操作和页的访问次数,有利于提高效率
此时由用户来控制堆的线程安全,可以用互拆量等对线程操作堆的操作进行同步
若要分配大量内存,如1M或更多,建议用VirtualAlloc来分配内存而不是在堆中分配内存
4.堆中分配内存的步骤
(1)遍历已分配内存和未分配内存的内存块的链表
(2)找到一块足够大的内存模块
(3)在找到的内存模块上分配内存,并将其标记为已分配
(4)在内存链表中加入新的条目
5.Common API:
HeapCreate HeapSize HeapReAlloc
HeapFree HeapDestroy GetProcessHeaps 取得堆的句柄
HeapValidate HeapCompact
HeapLock HeapUnlock
HeapWalk
堆非常适合分配大量的小型数据。它是用来管理链表和树的最佳方式。但是它分配和释放内存块的速度比虚拟内存和内存映射文件要慢,而且也无法再对物理存储器的调拨和撤销调拨进行直接控制。
一个进程同时可以有多个堆,进程在整个生命周期内可以创建和销毁这些堆。但是,默认的堆是在进程开始运行之前由系统自动创建的,在进程终止后会自动销毁。我们无法销毁进程的默认堆。每个堆都有一个用来标识自己的堆句柄,所有分配和释放内存块的堆函数都会在参数中用到这个堆句柄。
我们可以调用如下函数来得到进程默认堆得句柄:
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,那么创建的堆会有一个最大大小。
如果dwMaximumSize为0,那么创建的堆将是可增长的,直到用尽所有的物理存储器为止。
从堆中分配内存块
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_EXCEPTIONS,HEAP_NO_SERIALIZE,HEAP_ZERO_MEMORY, HEAP_REALLOC_IN_PLACE_ONLY。
HEAP_ZERO_MEMORY:这个标志只有在增大内存块的大小时才有用。在这种情况下,内存中的额外字节会被清零。减小内存块的大小时,这个标志不起任何作用。
HEAP_REALLOC_IN_PLACE_ONLY:在增大内存块的时候,HeapReAlloc可能会在堆内部移动内存块,而这个标志用来告诉HeapReAlloc不要移动内存块。
如果HeapReAlloc函数能在不移动内存块的前提下就能让它增大,那么函数会返回原来的内存块地址。另一方面,如果HeapReAlloc必须移动内存块的地址,那么函数将返回一个指向一块更大内存块的新地址。
如果一个内存块是链表或者树的一部分,那么需要指定这个标志。因为链表或者树的其他节点可能包含指向当前节点的指针,把节点移动到堆中其他的地方会破坏链表或树的完整性。
lpMem和dwBytes参数分别用来指定想要调整大小的内存块的原始地址以及内存块的新大小,以字节为单位。
获得内存块的大小
分配一块内存后,可以调用如下函数来得到这块内存的实际大小:
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函数允许我们枚举进程中的堆以及堆中分配的内存块。
Heap32First,Heap32Next, 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,并传NULL给lpMem。
该函数会遍历堆中的各个内存块,确保没有任何一块内存被破坏。
4.为了让堆中闲置的内存块能重新接合在一起,并撤销调拨给堆中闲置内存块的存储器,可以调用如下函数:
SIZE_T WINAPI HeapCompact(
__in HANDLE hHeap,
__in DWORD dwFlags
);
一般来说,我们会传0 给dwFlags参数。
1. 堆适合分配小内存块,不需要按分配粒度或者页大小对齐。堆在最初只是预定了一块区域,在客户分配时将预定的区域提交,在客户释放后可能反提交。
2. 关于默认堆:GetProcessHeap返回,用户模式代码无法销毁它,在进程结束后由系统销毁。进程可以通过链接选项“/HEAP:reserve[,commit]”来设置默认堆大小。因为默认堆属于进程,所以在DLL中不应设置该链接选项。Windows的ANSI版API向Unicode版转化的时候从默认堆分配字符串缓存,LocalAlloc、GlobalAlloc也从默认堆分配内存。默认堆对外界访问进行了同步,即没有使用HEAP_NO_SERIALIZE标记。
3. 使用独立堆的一些好处:(1)写堆内存出错后,不会影响其他堆的数据。(2)对特定类型数据使用独立堆的话,由于分配块大小相同,具有速度快、无碎片的优点。(3)相关数据使用独立的堆,在访问这些数据时访问的页面更集中,减少PageFault。(4)对特定线程上的逻辑结构使用独立堆,不必加锁,提高性能。
4. HeapCreate:参数fdwOption,如果在创建堆的时候指定了部分标志(如HEAP_NO_SERIALIZE标志等),以后每次访问堆这些标志都生效;如果创建的时候没有指定,那后续的每次访问可以单独指定标志。 HEAP_NO_SERIALIZE-访问堆的时候不加锁。HEAP_GENERATE_EXCEPTIONS-分配内存失败的时候抛出异常,默认行为是返回NULL。HEAP_CREATE_ENABLE_EXECUTE-可以在堆内存上放置代码来执行。参数dwInitalSize-初始堆大小。参数dwMaximumSize-如果非0,表示如果堆内存使用量达到这个值后再分配会失败;为0,表示堆会自动增大,直到内存用尽。
5. HeapAlloc、HeapSize、HeapFree、HeapDestroy,容易理解。
6. HeapReAlloc:HEAP_ZERO_MEMORY-增大内存时,增加的字节初始化为0。HEAP_REALLOC_IN_PLACE_ONLY-要求不移动起始地址的情况下改变大小,需要增大时如果当前位置剩余空间不足会返回NULL。
7. HeapSetInformation:标记HeapEnableTerminationOnCorruption-Visita以上使用。默认情况下,堆内存被破坏后只在调试器中触发一个断言然后继续执行,这个标记允许发现堆破坏就抛出异常。该标记影响进程中所有堆,无法清空标记。标记HeapCompatibilityInformation-值为2的时候,表示启用低碎片堆(lowfragmentation heap)算法,启用该算法的堆针对内存碎片问题优化有更好的性能。
8. Heap32ListFirst、Heap32ListNext-遍历快照(CreateToolhelp32Snapshot)中的堆。Heap32First、Heap32Next-遍历指定堆中的块。GetProcessHeaps-获得包括默认堆在内的所有堆句柄。HeapValidate-检查指定堆中所有块或者单个块的有效性。HeapCompact-将堆中闲置块合并,并反提交。HeapLock、HeapUnlock-锁定堆。HeapWalk-遍历指定堆中的块,建议先锁堆。