首页 IP地址查询 | Alexa排名查询 | 手机归属地查询
设为首页 收藏本站
  • 网络编程网络编程
  • 软件编程软件编程
  • 数据库技术数据库技术
  • 编程学院
  • 业界资讯 业界资讯
  • 源码中心源码中心
  • 会员中心会员中心
  • 页面导航: 首页C++编程系统 → 内存管理机制--堆 (Heap)

    内存管理机制--堆 (Heap)

    发布:黄海 发布日期:2009-02-28 字体:[增加 减小] 类型:转载
    本文目的:
    Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存
     
    一.使用场合
    堆是进程创建时在进程空间建立的区域,由堆管理器来管理。一个进程可以有很多个堆。进程有一个默认堆为1M,可以动态的扩大。
    当程序需要管理很多小对象时,适合用堆;当需要的空间大于1M时,最好用虚拟内存来管理。
    堆的优点是,有堆管理器来替它管理,不需管理具体的事情如页面边界
            和分配粒度等问题,你可以从调用函数看的出来,比VirtualAlloc的参数少了
            不少。
             堆的缺点是分配和释放的速度比前几种机制要慢,所以最好不要超过
             1M;不像虚拟内存那样随时提交和释放,因为它是由堆管理器决定的。如果
             用堆分配1G的空间,需要1分种,而用虚拟内存,则感觉不到任何延迟。 
    二.默认堆
    进程默认堆是供所有线程使用的,每当线程需要从堆中分配释放内存区时,系
    统会同步堆,所以访问速度较慢。
    它的默认大小是1M,同样的,你可以通过以下链接命令改变其大小:
    #pragma comment(linker,"/HEAP:102400000,1024000")
    第一个值是堆的保留空间,第二个值是堆开始时提交的物理内存大小。本文将堆改变为100M
     
    三.自建堆
    优点:
    保护数据结构:
    将不同的数据结构存在不同的堆中,可以防止不同的结构之间由于指针误操作而破坏了它们。
    消除内存碎片:
    将大小不同的结构保存在一个堆中,会导致碎片的产生,比如释放一个小结构时,大结构也不能利用它。
    独享堆的快速:
    如果用默认堆的话,线程之间是同步访问,速度慢;如果创建独享堆,则系统可以不需同步,比较快。
    第二个快速体现在释放的快速,默认堆中,你只能释放某个内存块,而不能释放整个堆;而独享堆可以一次释放堆,也就是释放了所有的内存块。
     
    A)建立堆:
    使用以下API  
                HANDLE HeapCreate(DWORD 选项,SIZE_T 初始大小,SIZE_T 最大值)
    选项取值为0 ,不是以下任意一个
    HEAP_NO_SERIALIZE,系统无需同步堆
    HEAP_GENERATE_EXCEPTIONS,当创建失败或分配失败时产生异常。
    初始大小是堆的大小,系统会规整到页面的整数倍,如0~4096的任何数都为4096;但是,进程空间至少要64K
    最大值是堆允许的最大值;为0则无限。
    使用HEAP_NO_SERIALIZE需确定只有单线程访问这个堆,否则有可能破坏堆;或程序有同步代码来同步堆。
    C++程序如下:
    pHeap=(char*)GetProcessHeap();
    printf("默认堆地址=%x\n",pHeap);
     
    MEMORYSTATUS memStatus2;
                GlobalMemoryStatus(&memStatus2);
    HANDLE hHeap=HeapCreate(HEAP_NO_SERIALIZE|HEAP_GENERATE_EXCEPTIONS,1024*1024*50,0);
                char* pHeap=(char*)hHeap;
                printf("新建堆1地址=%x\n",pHeap);  
                if(hHeap==NULL)
                {
                            cout<<"创建堆失败!"<<endl;
                }
                MEMORYSTATUS memStatus3;
                GlobalMemoryStatus(&memStatus3);
                cout<<"建立堆后:"<<endl;
    cout<<"减少物理内存="<<memStatus2.dwAvailPhys-memStatus3.dwAvailPhys<<endl;
    cout<<"减少可用页文件="<<memStatus2.dwAvailPageFile-memStatus3.dwAvailPageFile<<endl;
    cout<<"减少可用进程空间="<<memStatus2.dwAvailVirtual-memStatus3.dwAvailVirtual<<endl<<endl;
     
    HANDLE hHeap2=HeapCreate(HEAP_NO_SERIALIZE|HEAP_GENERATE_EXCEPTIONS,1024*1024*10,0);
                char* pHeap2=(char*)hHeap2;
                printf("新建堆2地址=%x\n",pHeap2);
     
                结果如下:
               
    当建立堆1时,它分配了50M的物理内存给堆使用;当建立堆2时,堆2的地址是0x04bc 0000=0x019c 0000+50*1024*1024.
     
     
    B)分配内存:
               使用以下API
                PVOID HeapAlloc(HANDLE 堆句柄,DWORD 选项,SIZE_T 字节数)
                “选项可以是,
                HEAP_ZERO_MEMORY,所有字节初始化为0
                HEAP_NO_SERIALIZE,堆这个内存区独享
    HEAP_GENERATE_EXCEPTIONS,产生异常。如果创建堆有了它就不用再设了。异常可能为:STATUS_NO_MEMOR(无足够内存)和STATUS_ACCESS_VIOLATION(堆被破坏,分配失败)。
     
    C++程序如下:
    GlobalMemoryStatus(&memStatus3);
    PVOID pV=HeapAlloc(hHeap,
    HEAP_ZERO_MEMORY|HEAP_NO_SERIALIZE|HEAP_GENERATE_EXCEPTIONS,1024*507);
                if(pV==NULL)
                {
                            cout<<"分配堆内存失败!"<<endl;
                }
                char * pC=(char*)pV;
                printf("第一次分配地址=%x\n",pC);
               
                MEMORYSTATUS memStatus4;
                GlobalMemoryStatus(&memStatus4);
                cout<<"第一次堆分配后:"<<endl;
    cout<<"减少物理内存="<<memStatus3.dwAvailPhys-memStatus4.dwAvailPhys<<endl;
    cout<<"减少可用页文件="<<memStatus3.dwAvailPageFile-memStatus4.dwAvailPageFile<<endl;
    cout<<"减少可用进程空间="<<memStatus3.dwAvailVirtual-memStatus4.dwAvailVirtual<<endl<<endl;
     
    PVOID pV2=HeapAlloc(hHeap,
    HEAP_ZERO_MEMORY|HEAP_NO_SERIALIZE|HEAP_GENERATE_EXCEPTIONS,1024*508);
                if(pV2==NULL)
                {
                            cout<<"分配堆内存失败!"<<endl;
                }
                char * pC2=(char*)pV2;
                printf("第二次分配地址=%x\n",pC2);
                MEMORYSTATUS memStatus5;
                GlobalMemoryStatus(&memStatus5);
                cout<<"第二次堆分配后:"<<endl;
    cout<<"减少物理内存="<<memStatus4.dwAvailPhys-memStatus5.dwAvailPhys<<endl;
    cout<<"减少可用页文件="<<memStatus4.dwAvailPageFile-memStatus5.dwAvailPageFile<<endl;
    cout<<"减少可用进程空间="<<memStatus4.dwAvailVirtual-memStatus5.dwAvailVirtual<<endl<<endl;
                for(int i=0;i<200*1024;i++)
                            pC2[i]=9;
     
                MEMORYSTATUS memStatus10;
                GlobalMemoryStatus(&memStatus10);
                cout<<"第二次堆使用一半后:"<<endl;
    cout<<"减少物理内存="<<memStatus5.dwAvailPhys-memStatus10.dwAvailPhys<<endl;
    cout<<"减少可用页文件="<<memStatus5.dwAvailPageFile-memStatus10.dwAvailPageFile<<endl;
    cout<<"减少可用进程空间="
    <<memStatus5.dwAvailVirtual-memStatus10.dwAvailVirtual<<endl<<endl;
    结果如下:
    可以看出,第一次分配507K的地址为0x04ad d650<0x04bc 0000,它是在堆中分配的;第二次分配508K的地址为0x055c 0020>0x04bc 0000,它是在堆外分配的;无论在多大的堆中,只要分配内存块大于507K时,都会在堆外分配,但是,它像在堆中一样,存在堆的链接表中,受堆管理。分配时,系统使用的是虚拟页文件;只有在真正使用时,才会分配物理内存。
    至于为什么分配大于507K会在堆外分配而不直接使用堆中的内存,目前仍然不清楚。
     
     
    C)改变大小:
    PVOID HeapReAlloc(HANDLE 堆句柄,DWORD 选项,PVOID 旧内存块地址,SIZE_T 新内存块大小)
    选项除了以上三个外,还有HEAP_REALLOC_IN_PLACE_ONLY,指定不能移动原有内存块的地址。
    C++程序如下:
    GlobalMemoryStatus(&memStatus4);
               PVOID pV2New=HeapReAlloc(hHeap,0,pV2,1024*1024*2);
                if(pV2New!=NULL)
                {
                char * pC2New=(char*)pV2New;
                printf("改变分配地址=%x\n",pC2New);
                cout<<pC2New[0]<<endl;
                //cout<<pC2[0]<<endl;出现访问违规
                SIZE_T lenNew=HeapSize(hHeap,0,pV2New);
                cout<<"改变后大小="<<lenNew<<endl;
                }
                GlobalMemoryStatus(&memStatus5);
                cout<<"改变分配后:"<<endl;
    cout<<"减少物理内存="<<memStatus4.dwAvailPhys-memStatus5.dwAvailPhys<<endl;
    cout<<"减少可用页文件="<<memStatus4.dwAvailPageFile-memStatus5.dwAvailPageFile<<endl;
    cout<<"减少可用进程空间="
    <<memStatus4.dwAvailVirtual-memStatus5.dwAvailVirtual<<endl<<endl;
    结果如下:
    可以看出,新内存块紧接着原来内存块结束的地方开始创建,大小为2M;原来的内存块的内容被销毁和释放,所以新内存块只减少了增加的内存量。一个缺点就是,新内存块居然不保留原来内存的内容!另外,如果采用HEAP_REALLOC_IN_PLACE_ONLY的话,出现Not Enough Quote异常。也就是说,当前内存的状况是,必须移动才可以扩大此内存块。
             查询内存:
                可以查询堆中一个内存块的大小。
                SIZE_T HeapSize(HANDLE 堆句柄,DWORD 选项,LPVOID 内存块地址)
                “选项可为0HEAP_NO_SERIALIZE
                参考以上例子。
             释放内存块:
                BOOL HeapFree(HANDLE 堆句柄,DWORD 选项,PVOID 内存块地址)
                “选项可为0HEAP_NO_SERIALIZE
                C++程序如下:
                GlobalMemoryStatus(&memStatus5);
                HeapFree(hHeap,0,pV2New);
                MEMORYSTATUS memStatus6;
                GlobalMemoryStatus(&memStatus6);
                cout<<"第二次堆分配释放后:"<<endl;
    cout<<"增加物理内存="<<memStatus6.dwAvailPhys-memStatus5.dwAvailPhys<<endl;
    cout<<"增加可用页文件="<<memStatus6.dwAvailPageFile-memStatus5.dwAvailPageFile<<endl;
    cout<<"增加可用进程空间="<<memStatus6.dwAvailVirtual-memStatus5.dwAvailVirtual<<endl<<endl;
    结果如下:
                内存空间释放了原来的2M空间。
     
    D)释放堆:
               BOOL HeapDestroy(HANDLE 堆句柄)
                不能用它释放默认堆,系统忽略它的处理。
    这一次,我们先在堆1中分配了70M的内存,由于它很大,所以,堆在堆外给它分配了内存,所以,堆1一共有50M+70M=120M。释放程序如下:
    PVOID pV4=HeapAlloc(hHeap,HEAP_ZERO_MEMORY|HEAP_NO_SERIALIZE|HEAP_GENERATE_EXCEPTIONS)
    ,1024*1024*70);
                if(pV4==NULL)
                {
                            cout<<"分配堆内存失败!"<<endl;
                }
                char * pC4=(char*)pV4;
                printf("第四次堆分配=%x\n",pC4);
                MEMORYSTATUS memStatus9;
                GlobalMemoryStatus(&memStatus9);
                cout<<"分配堆内存后:"<<endl;
    cout<<"减少物理内存="<<memStatus7.dwAvailPhys-memStatus9.dwAvailPhys<<endl;
    cout<<"减少可用页文件="<<memStatus7.dwAvailPageFile-memStatus9.dwAvailPageFile<<endl;
    cout<<"减少可用进程空间="<<memStatus7.dwAvailVirtual-memStatus9.dwAvailVirtual<<endl<<endl;
     
                SIZE_T len=HeapSize(hHeap,0,pV4);
                cout<<"len="<<len<<endl;
                bool re=HeapDestroy(hHeap);
                if(re==false)
                {
                            cout<<"释放堆失败!"<<endl;
    }
    MEMORYSTATUS memStatus8;
                GlobalMemoryStatus(&memStatus8);
                cout<<"释放堆后:"<<endl;
    cout<<"增加物理内存="<<memStatus8.dwAvailPhys-memStatus9.dwAvailPhys<<endl;
    cout<<"增加可用页文件="<<memStatus8.dwAvailPageFile-memStatus9.dwAvailPageFile<<endl;
    cout<<"增加可用进程空间="<<memStatus8.dwAvailVirtual-memStatus9.dwAvailVirtual<<endl<<endl;
     
    结果如下:
    如所猜想一样,释放了120M内存。
     
     
    E)获取所有堆:
               DWORD GetProcessHeaps(DWORD 数量,PHANDLE 句柄数组)
                “数量是你想获取的堆数目;
                “句柄数组是获得的堆句柄。
                默认堆也可以获取。
                HANDLE        handles[10];
                memset(handles,0,sizeof(handles));
                GetProcessHeaps(10,handles);
                for(int i=0;i<10;i++)
                            cout<<""<<i+1<<"="<<handles[i]<<endl;
                结果如下:
               
    可以看见,一共有8个堆,堆1是默认堆,堆7和堆8是本文建立的堆。另外5个不知来源。
             验证堆:
                BOOL HeapValidate(HANDLE 堆句柄,DWORD 选项,LPVOID 内存块地址)
                “选项可为0HEAP_NO_SERIALIZE
                “内存块地址NULL时,验证所有内存块。
                C++程序如下:
    HANDLE        handles[10];
                memset(handles,0,sizeof(handles));
                GetProcessHeaps(10,handles);
                for(int i=0;i<10;i++)
                {
                            cout<<""<<i+1<<"="<<handles[i]<<"   ";
                            if(HeapValidate(handles[i],0,NULL))
                                        cout<<"验证堆成功!"<<endl;
                            else
                                        cout<<endl;
     
                }
    结果如下:
            合并内存块:
                UINT HeapCompact(HANDLE 堆句柄,DWORD 选项)
                “选项可为0HEAP_NO_SERIALIZE
                此函数可以合并空闲内存块。
           
            其他函数:
                HeapLockHeapUnlock 通常是系统使用的;
                HeapWalk可以遍历堆内存,需要以上两个函数。

    Tags: 内存
    为配合网络严查,文章评论将关闭敬请谅解.
    同 类 文 章
    最 近 更 新
    热 点 排 行