本文背景:
在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。
本文目的:
对Windows内存管理机制了解清楚,有效的利用C++内存函数管理和使用内存。
本文内容:
本文一共有六节,由于篇幅较多,故按节发表。其他章节请看本人博客的Windows内存管理及C++内存分配实例(一)(三)(四)(五)和(六)。
2. 内存状态查询函数
2.1系统信息
Windows 提供API可以查询系统内存的一些属性,有时候我们需要获取一些页面大小、分配粒度等属性,在分配内存时用的上。
请看以下C++程序:
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
cout<<"机器属性:"<<endl;
cout<<"页大小="<<sysInfo.dwPageSize<<endl;
cout<<"分配粒度="<<sysInfo.dwAllocationGranularity<<endl;
cout<<"用户区最小值="<<sysInfo.lpMinimumApplicationAddress<<endl;
cout<<"用户区最大值="
<<sysInfo.lpMaximumApplicationAddress<<endl<<endl;
结果如下:
可以看出,页面大小是4K,区域分配粒度是64K,进程用户区是0x0001 0000~0x7FFE FFFF。
2.2内存状态
· 内存状态可以获取总内存和可用内存,包括页文件和物理内存。
请看以下C++程序:
MEMORYSTATUS memStatus;
GlobalMemoryStatus(&memStatus);
cout<<"内存初始状态:"<<endl;
cout<<"内存繁忙程度="<<memStatus.dwMemoryLoad<<endl;
cout<<"总物理内存="<<memStatus.dwTotalPhys<<endl;
cout<<"可用物理内存="<<memStatus.dwAvailPhys<<endl;
cout<<"总页文件="<<memStatus.dwTotalPageFile<<endl;
cout<<"可用页文件="<<memStatus.dwAvailPageFile<<endl;
cout<<"总进程空间="<<memStatus.dwTotalVirtual<<endl;
cout<<"可用进程空间="<<memStatus.dwAvailVirtual<<endl<<endl;
结果如下:
可以看出,总物理内存是1G,可用物理内存是510兆,总页文件是2.5G,这个是包含物理内存的页文件;可用页文件是1.9G。这里还标识了总进程空间,还有可用的进程空间,程序只用了22兆的内存空间。这里说的都是大约数。
内存繁忙程序是标识当前系统内存管理的繁忙程序,从0到100,其实用处不大。
· 在函数里面静态分配一些内存后,看看究竟发生什么
char stat[65536];
MEMORYSTATUS memStatus1;
GlobalMemoryStatus(&memStatus1);
cout<<"静态分配空间:"<<endl;
printf("指针地址=%x/n",stat);
cout<<"减少物理内存="<<memStatus.dwAvailPhys-memStatus1.dwAvailPhys<<endl;
cout<<"减少可用页文件="<<memStatus.dwAvailPageFile-memStatus1.dwAvailPageFile<<endl;
cout<<"减少可用进程空间="<<memStatus.dwAvailVirtual-
memSta tus1.dwAvailVirtual<<endl<<endl;
结果如下:
可以看出,物理内存、可用页文件和进程空间都没有损耗。因为局部变量是分配在线程堆栈里面的,每个线程系统都会建立一个默认1M大小的堆栈给线程函数调用使用。如果分配超过1M,就会出现堆栈溢出。
· 在函数里面动态分配300M的内存后,看看究竟发生什么
char *dynamic=new char[300*1024*1024];
MEMORYSTATUS memStatus2;
GlobalMemoryStatus(&memStatus2);
cout<<"动态分配空间:"<<endl;
printf("指针地址=%x/n",dynamic);
cout<<"减少物理内存="<<memStatus.dwAvailPhys-memStatus2.dwAvailPhys<<endl;
cout<<"减少可用页文件="<<memStatus.dwAvailPageFile-memStatus2.dwAvailPageFile<<endl;
cout<<"减少可用进程空间="<<memStatus.dwAvailVirtual-memStatus2.dwAvailVirtual<<endl<<endl;
结果如下:
动态分配情况下,系统分配直到内存页文件使用完为止,当然,系统要留一下系统使用的页面。
2.3 进程区域地址查询
在给定一个进程空间的地址后,可以查询它所在区域和相邻页面的状态,包括页面保护属性、存储器类型等。
· C++静态分配了两次内存,一次是4K大一点,一个是900K左右。
char arrayA[4097];
char arrayB[900000];
第一次查询:
long len=sizeof(MEMORY_BASIC_INFORMATION);
MEMORY_BASIC_INFORMATION mbiA;
VirtualQuery(arrayA,&mbiA,len);
cout<<"静态内存地址属性:"<<endl;
cout<<"区域基地址="<<mbiA.AllocationBase<<endl;
cout<<"区域邻近页面状态="<<mbiA.State<<endl;
cout<<"区域保护属性="<<mbiA.AllocationProtect<<endl;
cout<<"页面基地址="<<mbiA.BaseAddress<<endl;
printf("arrayA指针地址=%x/n",arrayA);
cout<<"从页面基地址开始的大小="<<mbiA.RegionSize<<endl;
cout<<"邻近页面物理存储器类型="<<mbiA.Type<<endl;
cout<<"页面保护属性="<<mbiA.Protect<<endl<<endl;
第二次查询:
MEMORY_BASIC_INFORMATION mbiB;
VirtualQuery(arrayB,&mbiB,len);
cout<<"静态内存地址属性:"<<endl;
cout<<"区域基地址="<<mbiB.AllocationBase<<endl;
cout<<"区域邻近页面状态="<<mbiB.State<<endl;
cout<<"区域保护属性="<<mbiB.AllocationProtect<<endl;
cout<<"页面基地址="<<mbiB.BaseAddress<<endl;
printf("arrayB指针地址=%x/n",arrayB);
cout<<"从页面基地址开始的大小="<<mbiB.RegionSize<<endl;
cout<<"邻近页面物理存储器类型="<<mbiB.Type<<endl;
cout<<"页面保护属性="<<mbiB.Protect<<endl<<endl;
说明:区域基地址指的是给定地址所在的进程空间区域;
邻近页面状态指的是与给定地址所在页面状态相同页面的属性:MEM_FREE(空闲=65536)、MEM_RESERVE(保留=8192)和MEM_COMMIT(提交=4096)。
区域保护属性指的是区域初次被保留时被赋予的保护属性:PAGE_READONLY(2)、PAGE_READWRITE(4)、PAGE_WRITECOPY(8)和PAGE_EXECUTE_WRITECOPY(128)等等。
页面基地址指的是给定地址所在页面的基地址。
从页面基地址开始的区域页面的大小,指的是与给定地址所在页面状态、保护属性相同的页面。
邻近页面物理存储器类型指的是与给定地址所在页面相同的存储器类型,包括:MEM_PRIVATE(页文件=131072)、MEM_MAPPED(文件映射=262144)和MEM_IMAGE(exe映像=16777216)。
页面保护属性指的是页面被指定的保护属性,在区域保护属性指定后更新。
结果如下:
如前所说,这是在堆栈区域0x0004 0000里分配的,后分配的地址arrayB反而更小,符合堆栈的特性。arrayA和arrayB它们处于不同的页面。页面都受页文件支持,并且区域都是提交的,是系统在线程创建时提交的。
· C++动态分配了两次内存,一次是1K大一点,一个是64K左右。所以应该不会在一个区域。
char *dynamicA=new char[1024];
char *dynamicB=new char[65467];
VirtualQuery(dynamicA,&mbiA,len);
cout<<"动态内存地址属性:"<<endl;
cout<<"区域基地址="<<mbiA.AllocationBase<<endl;
cout<<"区域邻近页面状态="<<mbiA.State<<endl;
cout<<"区域保护属性="<<mbiA.AllocationProtect<<endl;
cout<<"页面基地址="<<mbiA.BaseAddress<<endl;
printf("dynamicA指针地址=%x/n",dynamicA);
cout<<"从页面基地址开始的大小="<<mbiA.RegionSize<<endl;
cout<<"邻近页面物理存储器类型="<<mbiA.Type<<endl;
cout<<"页面保护属性="<<mbiA.Protect<<endl<<endl;
VirtualQuery(dynamicB,&mbiB,len);
cout<<"动态内存地址属性:"<<endl;
cout<<"区域基地址="<<mbiB.AllocationBase<<endl;
cout<<"区域邻近页面状态="<<mbiB.State<<endl;
cout<<"区域保护属性="<<mbiB.AllocationProtect<<endl;
cout<<"页面基地址="<<mbiB.BaseAddress<<endl;
printf("dynamicB指针地址=%x/n",dynamicB);
cout<<"从页面基地址开始的大小="<<mbiB.RegionSize<<endl;
cout<<"邻近页面物理存储器类型="<<mbiB.Type<<endl;
cout<<"页面保护属性="<<mbiB.Protect<<endl;
结果如下:
这里是动态分配,dynamicA和dynamicB处于两个不同的区域;同样,页面都受页文件支持,并且区域都是提交的。
第二个区域是比64K大的,由分配粒度可知,区域至少是128K。那么,剩下的空间也是提交的吗,如果是的话那就太浪费了。看看就知道了:0x00E2 1000肯定在这个空间里,所以查询如下:
VirtualQuery((char*)0xE23390,&mbiB,len);
cout<<"动态内存地址属性:"<<endl;
cout<<"区域基地址="<<mbiB.AllocationBase<<endl;
cout<<"区域邻近页面状态="<<mbiB.State<<endl;
cout<<"区域保护属性="<<mbiB.AllocationProtect<<endl;
cout<<"页面基地址="<<mbiB.BaseAddress<<endl;
printf("dynamicB指针地址=%x/n",0xE21000);
cout<<"从页面基地址开始的大小="<<mbiB.RegionSize<<endl;
cout<<"邻近页面物理存储器类型="<<mbiB.Type<<endl;
cout<<"页面保护属性="<<mbiB.Protect<<endl;
结果如下:
可以看出,邻近页面状态为保留,还没提交,预料之中;0x00E1 0000 这个区域的大小可以计算出来:69632+978944=1024K。系统动态分配了1M的空间,就为了64K左右大小的空间。可能是为了使得下次有要求分配时时不用再分配了。
1. 内存管理机制--进程地址空间
http://blog.csdn.net/yeming81/archive/2008/01/16/2046193.aspx
3. 内存管理机制--虚拟内存 (VM)
http://blog.csdn.net/yeming81/archive/2008/01/17/2047879.aspx
4. 内存管理机制--内存映射文件 (Map)
http://blog.csdn.net/yeming81/archive/2008/01/18/2050521.aspx
5. 内存管理机制--堆 (Heap)
http://blog.csdn.net/yeming81/archive/2008/01/19/2052311.aspx
6. 内存管理机制--堆栈 (Stack)
http://blog.csdn.net/yeming81/archive/2008/01/19/2052312.aspx