堆简介
堆是Windows操作系统在运行时对系统资源的管理方式,是一种动态内存分配系统,可以在程序运行时动态地分配和释放内存。Windows提供了各种堆操作函数,如HeapCreate、HeapDestroy、HeapAlloc、HeapReAlloc、HeapFree等。
然而,在一般的程序开发过程中,我们往往不会直接使用这些底层的堆操作函数,因为这些函数的使用往往需要对Windows内存管理的深入理解和小心翼翼的错误处理。反之,我们通常会使用C和C++标准库提供的内存管理函数,如malloc、free、new、delete等。
这些函数提供了一层更高级的封装,让我们在编码时无需关心底层的内存分配细节。但请注意,这并不意味着这些函数的操作和底层的堆操作函数没有关系。实际上,它们的内部实现往往是基于底层的堆操作函数的。例如,当你调用malloc函数请求内存时,函数的内部实际上是调用了HeapAlloc函数来从进程的默认堆中分配内存的。同理,当你调用free函数释放内存时,函数的内部实际上是调用了HeapFree函数来释放内存的。新建对象的new操作符和删除对象的delete操作符也是类似的,只是它们还额外处理了对象的构造和析构。
此外,值得一提的是,Windows操作系统在内核层面提供了对内存分配效率的优化。例如,为了减少因内存碎片导致的性能问题,Windows引入了低碎片堆(Low Fragmentation Heap,简称LFH)。此外,Windows还支持使用内存池技术,如Lookaside Lists,来提高频繁分配和释放小块内存的性能。这些高级的内存管理技术可以极大地提高内存使用的效率和性能。
堆类型
在Windows系统中,堆有两种类型:普通堆和低碎片堆。
标准堆:它是最初的、最基本的HeapCreate函数创建的堆,使用FIFO的方式管理内存块。
低碎片堆(Low-Fragmentation Heap,简称LFH):微软在Windows XP SP2中为了解决内存碎片问题引入的新类型的堆。低碎片堆通过特殊的分配策略,提高了内存的利用率,降低了碎片。默认情况下,HeapCreate函数在Windows 8及以上版本创建的堆都是LFH。
堆使用
首先,我们使用HeapCreate(创建一个堆:
HANDLE hHeap;
hHeap = HeapCreate(0, 0, 0);
HeapCreate函数创建一个新的私有堆。第一个参数表示堆选项,一般设置为0。第二个和第三个参数表示堆的初始和最大大小,如果都设置为0,那么堆的大小将由系统自动管理。
然后,我们使用HeapAlloc函数在堆中分配内存:
PVOID pHeap;
pHeap = HeapAlloc(hHeap, 0, 10);
HeapAlloc函数在指定的堆(通过HeapCreate创建)中分配指定大小的内存块。第一个参数是堆句柄,第二个参数是内存分配选项,一般设置为0。第三个参数是需申请的内存大小,单位是字节。
之后,我们可以对这块内存进行读写:
memset(pHeap, 'a', 10); //初始化内存
然后,我们可以使用HeapReAlloc函数对分配的内存进行重新调整:
pHeap = HeapReAlloc(hHeap, 0, pHeap, 20);
HeapReAlloc函数改变指定内存块的大小。我们将原本10字节的内存重新调整为20字节。
完成操作后,我们需在堆中释放内存:
HeapFree(hHeap, 0, pHeap);
HeapFree函数释放在堆中预先分配的内存块。
最后,我们销毁堆:
HeapDestroy(hHeap);
完整的demo如下,我们将pHeap改为数组,并创建四个堆,每次通过printf函数打印出堆的地址:
#include <windows.h>
#include <string.h>
#include <stdio.h>
int main() {
HANDLE hHeap;
PVOID pHeap[4];
hHeap = HeapCreate(0, 0, 0);
if(hHeap == NULL) {
printf("堆创建失败!\n");
return -1;
}
for(int i = 0; i < 4; i++) {
pHeap[i] = HeapAlloc(hHeap, 0, 10*i+10);
if(pHeap[i] == NULL) {
printf("第%d个堆分配失败!\n", i+1);
for(int j = 0; j < i; j++)
HeapFree(hHeap, 0, pHeap[j]);
HeapDestroy(hHeap);
return -1;
}
printf("第%d个堆的地址:0x%p\n", i+1, pHeap[i]);
memset(pHeap[i], 'a', 10*i+10);
}
for(int i = 0; i < 4; i++)
HeapFree(hHeap, 0, pHeap[i]);
HeapDestroy(hHeap);
return 0;
}
堆地址从低到高,每次增加10字节,即第n个堆分配了 10*n+10
字节的内存。 所以:
第1个堆分配 10*1+10 = 20
字节。
第2个堆分配