Windows核心编程:堆

时间:2022-12-15 07:54:01




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标志才是安全地。

  • 进程中只有一个线程
  • 进程中有多个线程,但只有一个线程会访问这个堆
  • 进程中有多个线程,但进程使用了其它方式来管理对堆得独占访问   
    第二个参数 dwInitalSize表示一开始要调拨给堆得字节数。如果需要,HeapCreate会把这个值向上取整到CPU页面大小的整数倍。
    最后一个参数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);