• 内存管理[3]堆


    VirtualAlloc 分配的内存是以 4K 为最小单位、连续的内存地址(但映射到真实的内存时它不一定是连续的), 前面说了, 它不适合分配小内存(譬如只有几个字节的变量); 局部的变量在 "栈" 中有程序自动管理, 那么那些全局的小变量怎么办呢? 这就要用到 "堆".

    这样看来, VirtualAlloc 分配的内存既不是 "栈" 也不是 "堆"; VirtualAlloc 分配的内存地址是连续的, "堆" 中内容一般是不连续的, 所以管理 "堆" 比较麻烦, 它是通过双线链表的结构方式管理的; 程序可以拥有若干个 "堆", 每一个 "堆" 都会有一个句柄, 访问 "堆" 中的内容时先要找到这个 "堆", 然后再遍历链表, 这可能就是 "堆" 比 "栈" 慢的根本原因.



    在 "堆" 中分配内存(HeapAlloc)前先要建立 "堆"(HeapCreate), 就像程序有默认的 "栈" 一样, 每一个程序都有一个默认建立的 "堆"(可以用 GetProcessHeap 获取这个 "默认堆" 的句柄), 我们在 Delphi 中用到 "堆" 时, 使用的就是这个 "默认堆". 如果让程序更灵活地拥有多个 "堆", 必须要用到 API 函数.

    建立 "堆" 时会同时提交真实内存的, 这在申请大内存时会很慢, 所以默认堆也只有 1M, 但 "默认堆" 并没有限制大小, 它会根据需要动态增长.

    有了 "默认堆" 还有必要申请其他的 "堆" 吗? 这只有在多线程中才能体现出来, 和 "栈" 不一样, 程序会给每个线程分配一个 "栈区"; 而 "默认堆" 是进程中的所有线程公用的, 当一个线程使用 "默认堆" 时, 另一个需要使用 "堆" 的线程就要先挂起等待, 也就是它们不能同时使用; 只有通过 API 函数重新建立的私有堆才是互不干涉、最有效率的.



    先了解一下 "堆" 相关的函数.


    //建立堆; 注意建立时指定的尺寸也是按页大小(PageSize)对齐的, 譬如指定 15k, 实际会分配 16K.
    HeapCreate(
      flOptions: DWORD;     {堆属性选项, 见下表}
      dwInitialSize: DWORD; {初始尺寸, 单位是字节; 该大小会被直接提交到实际的内存}
      dwMaximumSize: DWORD  {最大尺寸, 如果不限定最大值就设为 0}
    ): THandle;             {返回堆句柄; 失败返回 0, 但如果参数 flOptions 允许了异常, 失败会返回异常标识}
    
    //flOptions 参数可选值:
    HEAP_NO_SERIALIZE        = 1; {非互斥, 此标记可允许多个线程同时访问此堆}
    HEAP_GENERATE_EXCEPTIONS = 4; {当建立堆出错时, 此标记可激发一个异常并返回异常标识}
    HEAP_ZERO_MEMORY         = 8; {把分配的内存初始化为 0}
    
    //flOptions 参数指定有 HEAP_GENERATE_EXCEPTIONS 时, 可能返回的异常:
    STATUS_ACCESS_VIOLATION = DWORD($C0000005); {参数错误}
    STATUS_NO_MEMORY        = DWORD($C0000017); {内存不足}

    //销毁堆
    HeapDestroy(
    hHeap: THandle {堆句柄}
    ): BOOL;       {}

    //从堆中申请内存
    HeapAlloc(
      hHeap: THandle; {堆句柄}
      dwFlags: DWORD; {内存属性选项, 见下表}
      dwBytes: DWORD  {申请内存的大小, 单位是字节}
    ): Pointer;       {返回内存指针; 失败返回 0 或异常, 情况和建立堆是一样}
    
    //dwFlags 参数可选值:
    HEAP_NO_SERIALIZE        = 1; {非互斥, 此标记可允许多个线程同时访问此堆}
    HEAP_GENERATE_EXCEPTIONS = 4; {当建立堆出错时, 此标记可激发一个异常并返回异常标识}
    HEAP_ZERO_MEMORY         = 8; {把分配的内存初始化为 0}
    
    {能看出这和堆的属性选项是一样的; 如果 dwFlags 参数设为 0, 将使用堆的属性; 如果重新指定将覆盖堆的属性}
    {另外: 如果堆是默认堆, 也就是堆句柄来自 GetProcessHeap, dwFlags 参数会被忽略}

    //改变堆内存的大小, 也就是重新分配
    HeapReAlloc(
      hHeap: THandle; {句柄}
      dwFlags: DWORD; {内存属性选项; 该参数比 HeapAlloc 多出一个选项, 见下表}
      lpMem: Pointer; {原内存指针}
      dwBytes: DWORD  {新的尺寸}
    ): Pointer;       {同 HeapAlloc}
    
    //dwFlags 参数可选值:
    HEAP_NO_SERIALIZE          = 1{非互斥, 此标记可允许多个线程同时访问此堆}
    HEAP_GENERATE_EXCEPTIONS   = 4{当建立堆出错时, 此标记可激发一个异常并返回异常标识}
    HEAP_ZERO_MEMORY           = 8{把分配的内存初始化为 0}
    HEAP_REALLOC_IN_PLACE_ONLY = 16; {此标记不允许改变原来的内存位置}

    //获取堆中某块内存的大小
    HeapSize(
      hHeap: THandle; {堆句柄}
      dwFlags: DWORD; {内存属性; 可选值是 0 或 HEAP_NO_SERIALIZE, 后者可确保同步访问}
      lpMem: Pointer  {内存指针}
    ): DWORD;         {成功返回字节为单位的大小; 失败返回 $FFFFFFFF}

    //释放堆中指定的内存块
    HeapFree(
      hHeap: THandle; {堆句柄}
      dwFlags: DWORD; {内存属性; 可选值是 0 或 HEAP_NO_SERIALIZE}
      lpMem: Pointer  {内存指针}
    ): BOOL;          {}

    //验证堆
    HeapValidate(
      hHeap: THandle; {}
      dwFlags: DWORD; {}
      lpMem: Pointer  {}
    ): BOOL;          {}

    //整理堆
    HeapCompact(
      hHeap: THandle; {}
      dwFlags: DWORD  {}
    ): UINT;          {}

    //锁定堆
    HeapLock(
      hHeap: THandle {}
    ): BOOL;         {}

    //锁定后的解锁
    HeapUnlock(
      hHeap: THandle {}
    ): BOOL;         {}

    //列举堆中的内存块
    HeapWalk(
      hHeap: THandle;                {}
      var lpEntry: TProcessHeapEntry {}
    ): BOOL;                         {}

    举例放下篇吧.

  • 相关阅读:
    ASP.NET 页面间传递参数的方法
    Javascript 检查字符串是否是数字的几种方法
    周鸿祎教你写商业计划书
    提高网站程序性能的十条建议
    启动IIS6下Gzip功能详细操作步骤
    IT创业前要深思的八大问题
    学了php才知道MVC是怎么回事
    不能访问windows installer 服务 的解决方法
    SNS 相关
    【技术】Ubuntu上位机配置Blackfin开发环境手记
  • 原文地址:https://www.cnblogs.com/h2zZhou/p/6649779.html
Copyright © 2020-2023  润新知