Windows核心编程:堆
在系统内部,堆就是一块预定的地址空间区域。刚开始,区域内的大部分页面都没有调拨物理存储器。随着我们不断地从堆中分配内存,对管理器会给对调拨越来越多的物理存储器。这些物理存储器始终是从页交换文件中分配的。释放对中的内存块时,堆管理器会撤销已调拨的物理存储器。
1.进程的默认堆
进程初始化的时候,系统会在进程的地址空间中创建一个堆。这个堆被称为进程的默认堆。在默认的情况下,这个堆的地址空间区域的大小事1MB。但是,系统可以增大进程的默认堆,使它大于1MB。我们也可以在创建应用程序的时候用/HEAP连接器开关来改变默认的区域大小。
为了转换字符串,ANSI版本的函数需要分配一块内存来保存Unicode版本的字符串。这块内存就是从进程的默认堆中分配的。许多其它的函数需要用到临时的内存块,这些内存块也是从进程的默认堆中分配的。系统保证不管什么时候,一次只让一个线程从默认堆中分配或释放内存块。
一个进程同时可以又多个堆,进程在整个生命周期内可以创建和销毁这些堆。但是,默认堆是在进程开始运行之前由系统自动创建的,在进程终止后悔自动销毁。我们无法销毁进程的默认堆。每个堆都有一个标识自己的堆句柄,所有分配和释放内存块的堆函数都会在参数中用到这个堆句柄。
我们可以通过调用GetProcessHeap来得到进程的默认堆得句柄:
HANDLE GetProcessHeap();
2.为什么要创建额外的堆
除了进程的默认堆,我们可以在进程的地址空间中创建额外的堆。由于以下原因,我们可能希望在应用程序中创建额外的堆:
- 对组件进行保护
- 更有效地内存管理
- 局部访问
- 避免线程同步的开销
- 快速释放
3.如何创建额外的堆
我们可以调用HeapCreate来在自己的进程中创建额外的堆:
HANDLE HeapCreate(
DWORD fdwOptions,
SIZE_T dwInitialSize,
SIZE_T dwMaximumSize);
第一个参数fdwOptions用来表示对堆得操作该如何进行。可以指定0、HEAP_NO_SERIALIZE,HEAP_GENERATE_EXCEPTIONS,HEAP_CREATE_ENABLE_EXECUTE或这些标志的组合。
如果没有指定HEAP_NO_SERIALIZE标志,线程将允许多个线程同时访问,这将引起错误。
当且仅当进程满足一下条件之一或更多的时候,使用HEAP_NO_SERIALIZE标志才是安全地。
- 进程中只有一个线程
- 进程中有多个线程,但只有一个线程会访问这个堆
- 进程中有多个线程,但进程使用了其它方式来管理对堆得独占访问
最后一个参数dwMaximumSize表示堆能增长到的最大大小。如果为0,那么创建的堆石可增长的,它没有一个指定的上限。
从堆中分配内存块
从对中分配一块内存只不过是调用一下HeapAlloc函数:
PVOID HeapAlloc(
HANDLE hHeap,
DWORD fdwFlags,
SIZE_T dwBytes);
第一个参数 hHeap是一个堆得句柄,表示要从哪个堆中分配内存。参数dwBytes表示要从堆中分配多少个字节。之间的参数fdwFlags用来指定一些标志,这些标志会对分配产生影响:HEAP_ZERO_MEMORY,HEAP_GENERATE_EXCEPTIONS和HEAP_NO_SERIALIZE。
调整内存块的大小
调整内存块的大小事通过调用HeapReAlloc函数来完成的:
PVOID HeapReAlloc(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem,//需要调整内存块的当前地址
SIZE_T dwBytes);//内存块的新大小
获得内存块大小
分配一块内存后,可以调用HeapSize函数来得到这块内存的实际大小:
SIZE_T HeapSize(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);//内存块地址
释放内存块
不再需要一块内存的时候,我们可以调用HeapFree来释放它:
BOOL HeapFree(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem);
销毁堆
如果应用程序不再需要自己创建的堆,则可以调用HeapDestroy来销毁它:
BOOL HeapDestroy(HANDLE hHeap);
调用HeapDestroy会释放堆中包含的所有内存块,同时系统会收回堆所占用的物理存储器和地址空间区域。
在C++中使用堆
class CSomeClass {
private:
static HANDLE s_hHeap;
static UINT s_uNumAllocsInHeap;
// Other private data and member functions
...
public:
void* operator new (size_t size);
void operator delete (void* p);
// Other public data and member functions
...
};
HANDLE CSomeClass::s_hHeap = NULL;
UINT CSomeClass::s_uNumAllocsInHeap = 0;
void* CSomeClass::operator new (size_t size) {
if (s_hHeap == NULL) {
// Heap does not exist; create it.
s_hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0, 0);
if (s_hHeap == NULL)
return(NULL);
}
// The heap exists for CSomeClass objects.
void* p = HeapAlloc(s_hHeap, 0, size);
if (p != NULL) {
// Memory was allocated successfully; increment
// the count of CSomeClass objects in the heap.
s_uNumAllocsInHeap++;
}
// Return the address of the allocated CSomeClass object.
return(p);
}
void CSomeClass::operator delete (void* p) {
if (HeapFree(s_hHeap, 0, p)) {
// Object was deleted successfully.
s_uNumAllocsInHeap--;
}
if (s_uNumAllocsInHeap == 0) {
// If there are no more objects in the heap,
// destroy the heap.
if (HeapDestroy(s_hHeap)) {
// Set the heap handle to NULL so that the new operator
// will know to create a new heap if a new CSomeClass
// object is created.
s_hHeap = NULL;
}
}
}
4.其它堆函数
除了前面已经介绍过的堆函数之外,Windows还提供了其它一些堆函数。
由于进程在自己的地址空间中可以又多个堆,所以GetProcessHeaps函数可以让我们得到这些堆得句柄:
DWORD GetProcessHeaps(
DWORD dwNumHeaps,
PHANDLE phHeaps);
HeapValidate用来验证堆得完整性:
BOOL HeapValidate(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
为了让堆中闲置的内存块能重新结合在一起,并撤销调拨给堆中闲置内存块的存储器,可以调用下面的函数:
UINT HeapCompact(
HANDLE hHeap,
DWORD fdwFlags);
下面两个函数用于线程同步:
BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnlock(HANDLE hHeap);