windows 堆管理

windows堆管理是建立在虚拟内存管理的基础之上的,每个进程都有独立的4GB的虚拟地址空间,其中有2GB的属于用户区,保存的是用户程序的数据和代码,而系统在装载程序时会将这部分内存划分为4个段从低地址到高地址依次为静态存储区,代码段,堆段和栈段,其中堆的生长方向是从低地址到高地址,而栈的生长方向是从高地址到低地址。 程序申请堆内存时,系统会在虚拟内存的基础上分配一段内存,然后记录下来这块的大小和首地址,并且在对应内存块的首尾位置各有相应的数据结构,所以在堆内存上如果发生缓冲区溢出的话,会造成程序崩溃,这部分没有硬件支持,所有管理算法都有开发者自己设计实现。 堆内存管理的函数主要有HeapCreate、HeapAlloc、HeapFree、HeapRealloc、HeapDestroy、HeapWalk、HeapLock、HeapUnLock。下面主要通过一些具体的操作来说明这些函数的用法。

堆内存的分配与释放

堆内存的分配主要用到函数HeapAlloc,下面是这个函数的原型:

LPVOID HeapAlloc(
  HANDLE hHeap, //堆句柄,表示在哪个堆上分配内存
  DWORD dwFlags, //分配的内存的相关标志
  DWORD dwBytes //大小
);

堆句柄可以使用进程默认堆也可以使用用户自定义的堆,自定义堆使用函数HeapCreate,函数返回堆的句柄,使用GetProcessHeap可以获取系统默认堆,返回的也是一个堆句柄。分配内存的相关标志有这样几个值: HEAP_NO_SERIALIZE:这个表示对堆内存不进行线程并发控制,由于系统默认会进行堆的并发控制,防止多个线程同时分配到了同一个堆内存,如果程序是单线程程序则可以添加这个选项,适当提高程序运行效率。 HEAP_ZERO_MEMORY:这个标志表示在分配内存的时候同时将这块内存清零。 HeapCreate函数的原型如下:

HANDLE HeapCreate(
  DWORD flOptions, //堆的相关属性
  DWORD dwInitialSize, //堆初始大小
  DWORD dwMaximumSize //堆所占内存的最大值
);

flOptions的取值如下: HEAP_NO_SERIALIZE:取消并发控制 HEAP_SHARED_READONLY:其他进程可以以只读属性访问这个堆 dwInitialSize, dwMaximumSize这两个值如果都是0,那么堆内存的初始大小由系统分配,并且堆没有上限,会根据具体的需求而增长。下面是使用的例子:

    //在系统默认堆中分配内存
    srand((unsigned int)time(NULL));
    HANDLE hHeap = GetProcessHeap();
    int nCount = 1000;
    float *pfArray = (float *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, nCount * sizeof(float));
    for (int i = 0; i < nCount; i++)
    {
        pfArray[i] = 1.0f * rand();
    }

    HeapFree(hHeap, HEAP_NO_SERIALIZE, pfArray);

    //在自定义堆中分配内存
    hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0, 0);
    pfArray = (float *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, nCount * sizeof(float));
    for (int i = 0; i < nCount; i++)
    {
        pfArray[i] = 1.0f * rand();
    }

    HeapFree(hHeap, HEAP_NO_SERIALIZE, pfArray);
    HeapDestroy(hHeap);

遍历进程中所有堆的信息:

便利堆的信息主要用到函数HeapWalk,该函数的原型如下:

BOOL WINAPI HeapWalk(
  __in          HANDLE hHeap,//堆的句柄
  __in_out      LPPROCESS_HEAP_ENTRY lpEntry//返回堆内存的相关信息
);

下面是PROCESS_HEAP_ENTRY的原型:

typedef struct _PROCESS_HEAP_ENTRY {
    PVOID lpData;
    DWORD cbData;
    BYTE cbOverhead;
    BYTE iRegionIndex;
    WORD wFlags;
    union {
        struct {
            HANDLE hMem;
            DWORD dwReserved[3];
            } Block;
        struct {
            DWORD dwCommittedSize;
            DWORD dwUnCommittedSize;
            LPVOID lpFirstBlock;
            LPVOID lpLastBlock;
            } Region;
        };
} PROCESS_HEAP_ENTRY,  *LPPROCESS_HEAP_ENTRY;

这个结构中的公用体具体使用哪个与wFlags相关,下面是这些值得具体含义:

wFlags

堆入口含义

lpData

cbData

cbOverhead块前堆数据结构大小

iRegionIndex

Block

Region

PROCESS_HEAP_ENTRY_BUSY

被分配的内存块

首地址

内存块大小

内存块前堆数据结构

所在区域索引

无意义

无意义

PROCESS_HEAP_ENTRY_DDESHARE

DDE共享内存块

首地址

内存块大小

内存块前堆数据结构

所在区域索引

无意义

无意义

PROCESS_HEAP_ENTRY_MOVEABLE

可移动的内存块(兼容GlobalAllocLocalAlloc)

首地址(可移动内存句柄的首地址)

内存块大小

内存块前堆数据结构

所在区域索引

与PROCESS_HEAP_ENTRY_BUSY标志一同指定可移动内存句柄值

无意义

PROCESS_HEAP_REGION

已提交的堆虚拟内存区域

区域开始地址

区域大小

区域前堆数据结构

区域索引

无意义

虚拟内存区域详细信息

PROCESS_HEAP_UNCOMMITTED_RANGE

未提交的堆虚拟内存区域

区域开始地址

区域大小

区域前堆数据结构

区域索引

无意义

无意义

下面是时遍历堆内存的例子:

    PHANDLE pHeaps = NULL;
    //当传入的参数为0和NULL时,函数返回进程中堆的个数
    int nCount = GetProcessHeaps(0, NULL);
    pHeaps = new HANDLE[nCount];
    //获取进程所有堆句柄
    GetProcessHeaps(nCount, pHeaps);
    PROCESS_HEAP_ENTRY phe = {0};
    for (int i = 0; i < nCount; i++)
    {
        cout << "Heap handle: 0x" << pHeaps[i] << '\n';
        //在读取堆中的相关信息时需要将堆内存锁定,防止程序向堆中写入数据
        HeapLock(pHeaps[i]);
        HeapWalk(pHeaps[i], &phe);
        //输出堆信息
        cout << "\tSize: " << phe.cbData << " - Overhead: "  
            << static_cast<DWORD>(phe.cbOverhead) << '\n';     
        cout << "\tBlock is a";         
        if(phe.wFlags & PROCESS_HEAP_REGION)  
        {       
            cout << " VMem region:\n";   
            cout << "\tCommitted size: " << phe.Region.dwCommittedSize << '\n';     
            cout << "\tUncomitted size: " << phe.Region.dwUnCommittedSize << '\n';    
            cout << "\tFirst block: 0x" << phe.Region.lpFirstBlock << '\n';      
            cout << "\tLast block: 0x" << phe.Region.lpLastBlock << '\n';    
        }           
        else     
        {      
            if(phe.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE)
            {             
                cout << "n uncommitted range\n"; 
            }          
            else if(phe.wFlags & PROCESS_HEAP_ENTRY_BUSY)   
            {      
                cout << "n Allocated range: Region index - "   
                    << static_cast<unsigned>(phe.iRegionIndex) << '\n';          
                if(phe.wFlags & PROCESS_HEAP_ENTRY_MOVEABLE)  
                {                
                    cout << "\tMovable: Handle is 0x" << phe.Block.hMem << '\n';  
                }               
                else if(phe.wFlags & PROCESS_HEAP_ENTRY_DDESHARE)       
                {                  
                    cout << "\tDDE Sharable\n";    
                }             
            }       
            else cout << " block, no other flags specified\n";        
        }          
        cout << std::endl;       
        HeapUnlock(pHeaps[i]);
        ZeroMemory(&phe, sizeof(PROCESS_HEAP_ENTRY));
    }
    delete[] pHeaps;

另外堆还有其他操作,比如使用HeapSize获取分配的内存大小,使用HeapValidate可以校验一个对内存的完整性,从而提早发现”野指针”等等。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 记一次内存泄露调试

    首先介绍一下相关背景。最近在测试一个程序时发现,在任务执行完成之后,从任务管理器上来看,内存并没有下降到理论值上。程序在启动完成之后会占用一定的内存,在执行任务...

    Masimaro
  • PE文件详解(三)

    在执行一个PE文件的时候,windows 并不在一开始就将整个文件读入内存的,二十采用与内存映射文件类似的机制。 也就是说,windows 装载器在装载的时...

    Masimaro
  • Windows内核中的内存管理

    其中PAGED_CODE是一个WDK中提供的一个宏,只在debug版本中生效,用于判断当前的中断请求级别,当级别高于DISPATCH_LEVEL(包含这个级别)...

    Masimaro
  • Android 开发如何做好内存优化

    Android的一个应用程序的内存泄露对别的应用程序影响不大。为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都会使用一个专有...

    非著名程序员
  • Android内存管理(一)官方文档介绍

    https://developer.android.com/topic/performance/memory-overview?hl=zh-cn

    Anymarvel
  • 博鳌亚洲论坛今天闭幕,这是最值得互联网行业关注的精华

    4月11日,历时4天的博鳌亚洲论坛2018年年会正式落下帷幕。在大会上,最受互联网行业关注的自然是马云、董明珠这两位超级IP的讲话了,马云在博鳌亚洲论坛上谈到了...

    罗超频道
  • 【专业技术】Android内存泄漏简介

    存在问题: 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露。 解决方案: 其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它...

    程序员互动联盟
  • 利用Reduce函数求两曲线的交点

    WolframChina
  • Android使用注解代替枚举节省系统内存开销的方法

    Java5以后开始支持枚举类型,枚举类型使用起来非常方便,其重要的作用是作为类型安全使用的。如果在不考虑系统内存开销的情况下大量的使用枚举也不会有什么问题。但是...

    砸漏
  • android内存优化

    刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成。其实Java中没有指针的概念,但...

    xiangzhihong

扫码关注云+社区

领取腾讯云代金券