1:堆就是一块预订的地址空间区域,系统会根据请求调拨物理存储器,堆很适合管理小型对象,使用堆不必理会分配粒度和页面大小
2:堆是进程相关的,一个进程可以有多个堆,默认堆的大小为1M,默认堆是线程安全的,所以如果一个进程只有一个线程,为了略微提高性能,应该创建自己的堆
3:默认堆是肯定会被创建的,API会使用默认堆
4:HANDLE GetProcessHeap();返回进程默认堆句柄
5:DLL没有堆
2:创建自定义堆的好处
假设有一个链表和一个二叉树
(1)安全:将链表和二叉树放在不同的堆中,这样能避免他们相互影响,防止链表错误影响到二叉树
(2)高效:如果每个堆都只包含相同大小的对象,效率更高并且不会产生内存碎片
(3)同样是高效:我们经常访问相邻的数据,比我我们会循环遍历链表,如果他们放在不同的堆中,我们就能在同一个页面访问更多的数据,并且,当CPU将页面唤出到页交换文件时,也会产生同样的特性,这样我们也相对的减少了内存和页交换文件交互的此处(这段话可能说的不明白,看不懂就看书吧)
(4)如果创建一个新堆,并告诉操作系统只有一个线程访问此堆,这样就节省了线程同步的开销
(5)如果我们在一个自己的堆中存特定的数据,当我们不需要这些数据时,可以直接释放整个堆而不必显式释放每个内存块
3:创建堆
HANDLE HeapCreate(
DWORD flOptions,
SIZE_T dwInitialSize, //初始调拨给堆的字节数,操作系统可能会向上取整到页面大小整数倍
SIZE_T dwMaximumSize //堆最大大小,如果不为0,超过最大大小会抛出异常,如果为0堆可以无限增长
);
//flOptions取值可为
0 //默认属性
HEAP_NO_SERIALIZE //创建线程不安全堆
HEAP_CREATE_ENABLE_EXECUTE //堆中可以执行代码,如果不设置此标志而执行其中的代码会抛出
//EXCEPTION_ACCESS_VIOLATION异常
HEAP_GENERATE_EXCEPTIONS //在堆中分配和重新分配内存块失败时抛出异常
如果系统发现堆被破坏,会引发一个断言,此外,我们可以调用一个函数,让系统发现堆被破坏产生一个异常
4:从堆中分配内存块
LPVOID HeapAlloc(
HANDLE hHeap,
DWORD dwFlags,
SIZE_T dwBytes
);
//dwFlags取值可为:
HEAP_ZERO_MEMORY //清零
HEAP_GENERATE_EXCEPTIONS //如果本次分配不成功则抛出一个异常,如果HeapCreate()和这里都未指定此标志
//分配不成功则返回NULL
HEAP_NO_SERIALIZE //本次分配不需要保证线程安全
//抛出的异常可能如下
STATUS_NO_MEMORY //堆内存不够
STATUS_ACCESS_VIOLATION //堆被破坏或者参数出错
1:如果分配大于1M的内存,应该使用VirtualAlloc()而不使用堆
2:低碎片堆
如果堆要分配大量大小不同的对象,建议使用低碎片堆,低碎片堆不能和HEAP_NO_SERIALIZE标志同时使用
堆管理器自己也会进行优化检查,会决定是否将堆切换为低碎片堆
ULONG value=2;
if(HeapSetInformation(hHeap,HeapCompatibilityInformation,&value,sizeof(value)))
{
//切换到低碎片堆成功
//hHeap也可以为GetProcessHeap(),也就是将进程默认堆切换为低碎片堆
}
如果代码运行在调试器下,一些调试选项可能会阻止切换到低碎片堆,可以把环境变量_NO_DEBUG_HEAP设为1,就可以关闭这些与堆相关的调试选项
5:调整内存块的大小
注意这里是调整内存块大小不是调整堆大小
LPVOID HeapReAlloc( //调整大小失败返回NULL
HANDLE hHeap,
DWORD dwFlags,
LPVOID lpMem, //想要调整的内存块的当前地址
SIZE_T dwBytes //调整的新大小
);
//dwFlags可以为如下值:
HEAP_GENERATE_EXCEPTIONS
HEAP_NO_SERIALIZE
HEAP_ZERO_MEMORY //只有在增大堆时才有效,增大的部分清零
HEAP_REALLOC_IN_PLACE_ONLY //只有在增大堆时才有效,告诉系统不要移动以前的内存块
//HeapReAlloc()可能在堆内部移动内存块,但这有可能破坏其中比如链表的数据,因为链表指向的
//指针堆移动后无效了,所以应该指定此标志,但指定了此标志,也就是说,如果堆在不移动的前提下
//增大可能会失败
6:获得内存块大小
SIZE_T HeapSize(
HANDLE hHeap,
DWORD dwFlags, //0或者HEAP_NO_SERIALIZE
LPCVOID lpMem
);
7:释放内存块
BOOL HeapFree(
HANDLE hHeap,
DWORD dwFlags, //0或者HEAP_NO_SERIALIZE
LPVOID lpMem
);
//调用此函数可能会使堆管理器撤销调拨的物理存储器,但这并不是一定的
8:销毁堆
BOOL HeapDestroy(HANDLE hd);
进程终止后会销毁所有堆,线程终止时,系统不会帮我们销毁堆
不能用此函数去销毁默认堆,会直接返回FALSE
9:在C++中使用堆,参见书上
10:一些其他的堆函数
GetProcessHeaps()获得进程所有堆的句柄,HeapValidate()验证堆的完整性,HeapCompact()将堆中所有闲置内存块拼贴起来
HeapLock()和HeapUnlock()锁定堆,是其线程安全,我们如果没有指定HEAP_NO_SERIALIZE属性,调用许多堆函数,其内部同样会调用HeapLock()和HeapUnlock()函数
HeapWalk()用于调试,允许我们遍历堆的内容