1. 堆非常适合分配大量小型数据
  2. 堆的优点是不必理会分配粒度和页面边界这类事情。缺点是分配和释放内存块的速度比其他方式慢,而且也无法对物理存储器的调拨和撤销调拨进行直接控制
  3. 堆就是一块预定的地址空间。刚开始,区域的大部分页面都没有调拨物理存储器。随着我们不断地从堆中分配内存,堆管理器会给堆调拨越来越多的物理存储器。这些物理存储器始终是从页交换文件中分配的。释放堆中的内存块时,堆管理器会撤销已调拨的物理存储器
  4. 进程的默认堆:
    • 进程初始化的时候,系统会在进程的地址空间中创建一个堆。这个堆被称为进程的默认堆(default heap)
    • 默认情况下,这个堆的地址空间区域是1MB。但是,系统可以增大进程的默认堆,使其大于1MB。我们也可以在创建应用程序的时候用/HEAP链接器开关改变默认的区域大小(使用方法:/HEAP:reserve[,ccommit])。由于DLL没有与之关联的堆,因此在创建DLL的时候不应该使用/HEAP开关。
    • 系统保证任何时候一次只让一个线程从默认堆中分配或释放内存块(即同步访问,线程安全)
  5. 一个进程同时可以有多个堆,进程在整个生命周期内可以创建和销毁这些堆。但是,默认堆是在进程开始运行之前由系统自动创建的,在进程终止后会自动销毁。我们无法销毁进程的默认堆。每个堆都有一个用来标识自己的堆句柄,可以通过调用GetProcessHeap来得到
  6. 额外的堆:
    • 为什么要创建额外的堆?
      • 对组件进行保护:一个链表和一个二叉树放在同一个堆中时,当出现问题时很难定位是链表出了问题还是二叉树出了问题;但如果放在两个堆中,就可以很容易定位是哪个的问题
      • 更有效的内存管理
      • 局部访问
      • 避免线程同步开销
      • 快速释放
    • 如何使用额外的堆?
      1. 调用HeapCreate来在自己的进程中创建额外的堆

        HANDLE HeapCreate(DWORD flOptions,
                          DWORD dwInitialSize,
                          DWORD dwMaximumSize);
        
        • flOptions:表示对堆的操作该如何进行,可以指定0、HEAP_NO_SERIALIZE、HEAP_GENERATE_EXCEPTIONS、HEAP_CREATE_ENABLE_EXECUTE或这些标志的组合 dwInitialSize:表示一开始要调拨给堆的字节数。如果需要,HeapCreate会把这个值取整到CPU页面大小的整数倍 dwMaximumSize:表示堆所能增长到的最大大小(即系统为堆所预定的地址空间的最大大小)。如果该值大于0,那么创建的堆会有一个最大大小;如果为0,那么创建的堆将是可增长的,没有上限
        • 在默认情况下,对堆的访问会依次进行,使多个线程可以从同一个堆中分配和释放内存块,同时也不会存在堆数据被破坏的危险。当任何程序试图从堆中分配一块内存的时候,HeapAlloc函数必须执行以下操作:
          1. 遍历已分配内存的链表和闲置内存的链表
          2. 找到一块足够大的闲置内存块
          3. 分配一块新的内存,也就是将刚找到的闲置内存块标记为已分配
          4. 将新分配的内存块添加到已分配内存的链表中
        • HEAP_NO_SERIALIZE标志取消多线程对堆的同步访问,使访问非线程安全;结合上文,多条线程若同时拿到一块闲置的内存块并分别将之标记为已分配内存,之后各线程分别对这块内存写入数据导致线程将读到错误的数据,导致无法预料的后果;应尽量避免使用该标记
        • HEAP_GENERATE_EXCEPTIONS标志告诉系统,每当在堆中分配或重新分配内存块失败的时候,抛出一个异常;我们还可以让堆管理器在任何Heap*函数中发现堆破坏,就抛出一个异常。只需添加以下代码:
        HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
        

        系统会将该策略严格应用到进程中的所有堆。此外,一旦为进程中所有的堆启用了这项特性,就再也无法禁用它了

        • 如果想在堆中存放可执行代码,则必须使用最后一个标记HEAP_CREATE_ENABLE_EXECUTE
      2. 调用HeapAlloc从堆中分配一块内存

        PVOID HeapAlloc(HANDLE hHeap,
                        DWORD fdwFlags,
                        SIZE_T dwBytes);
        
        • hHeap:堆句柄,表示要从哪个堆中分配内存 dwBytes:表示要从堆中分配多少个字节 dwFlags:指定分配标志,可以取HEAP_ZERO_MEMORY,HEAP_GENERATE_EXCEPTIONS和HEAP_NO_SERIALIZE;HEAP_ZERO_MEMORY会让HeapAlloc在返回之前把内存块的内容都清零;HEAP_GENERATE_EXCEPTIONS告诉系统,如果堆中没有足够的内存来满足内存分配请求,HeapAlloc应该抛出异常,如果在调用HeapCreate的时候指定了这个标志,调用HeapAlloc的时候就不需要再指定了,另一方面,可能希望在创建堆的时候不使用这个标志,在这种情况下,在调用HeapAlloc的时候指定这个标志就只会影响到当前这次调用;HEAP_NO_SERIALIZE强制系统不要把这次HeapAlloc调用与其他线程对同一个堆的访问依次排列起来(非线程安全)
        • 如果分配成功,HeapAlloc将返回内存块的地址。如果没有指定HEAP_GENERATE_EXCEPTIONS标志,那么当无法分配内存时HeapAlloc会返回NULL
        • 分配大块内存(>=1M)的时候应该避免使用堆函数,建议使用VirtualAlloc函数
        • 如果分配大量不同大小的内存块,那么堆管理器内部用来处理分配请求的默认算法可能会产生地址空间碎片:由于所有可用的内存块都不够大,因此系统将无法找到一块足够大的闲置内存块。在Windows XP和Windows Server 2003之后的版本中,可以强制操作系统在分配内存的时候使用一种叫地碎片堆的算法。在多处理器的机器上,低碎片堆的性能得到了极大的提高,可以调用HeapSetInformation来设置;堆管理器自己也会对所有的分配请求进行监控并进行一些内部优化
      3. 释放内存块 不再需要一块内存的时候,可以调用HeapFree来释放它:

        BOOL HeapFree(HANDLE hHeap,
                      DWORD dwFlags,
                      LPVOID lpMem);
        

        HeapFree会释放内存块,如果操作成功,它会返回TRUE。参数fdwFlags可以是0或者HEAP_NO_SERIALIZE。调用这个函数可能会使堆管理器撤销一些已经调拨的物理存储器,但并不是一定的。

      4. 销毁堆 如果程序不再需要自己创建的堆,则可以调用HeapDestroy来销毁它:

        BOOL HeapDestroy(HANDLE hHeap);
        
        • 调用HeapDestroy会释放堆中包含的所有内存块,同时系统会收回所占用的物理存储器和地址空间区域
        • 如果不在进程终止之前显式地销毁堆,那么系统会替我们销毁。但只有在进程终止的时候才会这么做。如果一个线程创建了堆,那么在线程终止的时候,堆是不会被销毁的
        • 在进程完全终止之前,系统不允许销毁进程的默认堆。如果把进程的默认堆句柄传给HeapDestroy,系统将直接忽略并返回FALSE
    • 其他堆函数
      • HeapReAlloc:调整内存块的大小
      • HeapSize:获得内存块的大小
      • 枚举进程的堆以及堆中分配的内存块:Heap32First、Heap32Next、Heap32ListFirst、Heap32ListNext
      • 得到堆句柄:GetProcessHeaps
      • 用户堆的线程同步:HeapLock、HeapUnLock

转载于:https://my.oschina.net/simplefocus/blog/293246

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值