请教DLL中二维数组动态分配内存存外部释放的疑惑

动态链接库中分配内存引起的问题-- windows已在XX.exe中触发一个断点 - DoubleLi - 博客园
动态链接库中分配内存引起的
&&&&&&&本文主要是探讨关于在动态链接库分配的内存在主程序中释放所产生的问题,该问题是我在刚做的PJP工程中所遇到的,由于刚碰到之时感动比较诡异(这也是学识不够所致),所以将它写下来,大家一起分享.
&&&&&&&问题来由:
由于该工程中要用到声音,所以我的分工之一就是用DirectMusic和DirectSound来开发声音播放的动态库,以提供给该工程的两个部分:仿真控制部分( 语音 )和三维部分( 场景声音 )使用,两个工程中的声音都以单独的线程播放,且两个线程几乎相同.,然而该动态库在以前的运行中一直没有出现过问题, 直到工程开发即将要结束阶段的前一个星期,我碰才到了这个问题,首先是三维部分中声音在第一次播放是没有问题,在播放第二个声音就出了问题(老是这样),但是仿真控制部分还是没有出现问题,我查不出错误,就用前一天的三维的版本来运行,重新生成后声音播放没有问题,初步以为是我同事当天写的代码有问题,可是第二天,同样的问题又发生了,还是那样,三维有问题,仿真部分没问题,怎么回事呢?我将三维的声音线程写成和仿真部分一样的,还是三维出问题,仿真没问题.我不相信是动态库中出问题,同一个动态库,同样的代码,只是在两个不同的程序里,为什么一个出问题,而另一个不出问题呢?
&&&&&&&出错信息如下:
&&&&&&&&&&&&&&&& &windows已在PowipD.exe中触发一个断点.&&&&&&&&&&&&&&&&& 其原因可能是堆被损坏,这也说明PowipD.exe中或它所加载的任何DLL中有bug.&&&&&&&&&&&&&&&&&&输出窗口可能提供了更多诊断信息
&&&&&&&按下中断后输入窗口出现的信息如下:
&&&&&&&&&&&&&& HEAP[PowipD.exe]:Invalid Address specified to RtlValidateHeap( 01CC58 )&&&&&&&&&&&&&& Windows已在PowipD.exe中触发一个断点.&&&&&&&&&&&&&& 输出窗口可能提供了更多诊断信息
&&&&&&&由于这些原因让我迷惑不解,到底是为什么出现这种奇怪的事情呢?问题出在那里?
问题的解决:
简单地说:DLL中分配的内存DLL要负责释放!(一个模块分配的内存要在同一个模块中释放!)
找到解决方法后我查看了三个工程的设置(三个工程皆用VS2005开发):
&&&&&&&三维:使用MDd(多线程调试DLL)运行期库
&&&&&&&仿真控制:使用MTd(多线程调试)运行期库
&&&&&&&声音动态库:使用MDd(多线程调试DLL)运行期库
于是,我将三个工程皆改为:使用MDd(多线程调试DLL)运行期库时,问题解决。
但是如果三个工程中有的不是用C/C++写的,或者其中有工程的设置已经不便更改了,那又有什么办法呢?
该问题主要是关于DLL与进程的地址空间的问题(下面是&&核心编程&&中的一段话):
单个地址空间是由一个可执行模块和若干个DLL模块组成,这些模块中,有些可以链接到静态版本的C/C++运行期库, 有些可以链接到一个DLL版本的运行期库,而有些模块(如果不是用C/C++编写的话)则根本不需要C/C++运行期库,许多开发人员经常会犯一个常见的错误,因为他们忘记了若干个C/C++运行期库可以存在于单个地址空间中.请看下面的代码(下面代码不是书上的):
DLL中如下:
&&&&&& int *DllFunc()
&&&&&& int *p =
EXE中如下:
void EXEFunc()
&&&&&& int *p = DllFunc();
&&&&&& if( p!= NULL )
&&&&&&&&&&&&&
如何看待这个问题呢?上面的代码能够正确运行吗?DLL函数分配的内存是由EXE的函数释放的吗?答案是可能的.上面显示的代码并没有提供足够的信息.如果EXE和DLL都链接到DLL的C/C++运行期库,那么上面的代码将能够很好地运行.但是,如果两个模块中的一个或者两个链接到静态C/C++运行期库,那个对delete的操作就会失败.
一个很方便的方法可以解决这个问题.当一个模块提供一个用于分配内存块的函数时,该模块也必须提供相应的释放内存的函数,将上面的代码改成如下的样子就不会出错了:
DLL中如下:
&&&&&& int *DllFunc()
&&&&&& int *p =
void DLLDelete( int *p)
&&&&&& if( p != NULL )
&&&&&&&&&&&&&
EXE中如下:
void EXEFunc()
&&&&&& int *p = DllFunc();
&&&&&& DLLDelete( p );
这样的代码才是正确的,当我将代码改成如上类似后,再将三维的运行期库改为原来一样(使用MDd(多线程调试DLL)运行期库),三维的声音也正确无误地播放出来了。当你在编写一个模块时,你时刻都得记着,别人的代码不一定是用C/C++写的,也有可能不能同时链接到DLL的C/C++运行期库。malloc和free函数也有类似的问题。
当然在你在生成你的模块时是可以选择运行期库的,VC 6.0配有6个运行期库:其描述如下:
用于单线程应用程序的静态应用程序的静态链接库&&&&&&
用于单线程应用程序的静态链接库的调试版
LibCMt.lib
用于多线程应用程序的静态链接库的发行版
LibCMtD.lib
用于多线程应用程序的静态链接库的调试版
MSVCRt.lib
用于动态链接MSVCRt.dll库的发行版的输入库
MSVCRtD.lib
用于动态链接MSVCRtD.dll的调试版的输入库。该库同时支持单线程应用程序和多线程应用程序
而VS2005中只配了4个C/C++运行期库,就是上表中的后面4个。
建议各位在软件开发时同一软件的不同模块最好使用一致的运行库,否则还可能出现链接问题。动态链接库中分配内存引起的
&&&&&&&本文主要是探讨关于在动态链接库分配的内存在主程序中释放所产生的问题,该问题是我在刚做的PJP工程中所遇到的,由于刚碰到之时感动比较诡异(这也是学识不够所致),所以将它写下来,大家一起分享.
&&&&&&&问题来由:
由于该工程中要用到声音,所以我的分工之一就是用DirectMusic和DirectSound来开发声音播放的动态库,以提供给该工程的两个部分:仿真控制部分( 语音 )和三维部分( 场景声音 )使用,两个工程中的声音都以单独的线程播放,且两个线程几乎相同.,然而该动态库在以前的运行中一直没有出现过问题, 直到工程开发即将要结束阶段的前一个星期,我碰才到了这个问题,首先是三维部分中声音在第一次播放是没有问题,在播放第二个声音就出了问题(老是这样),但是仿真控制部分还是没有出现问题,我查不出错误,就用前一天的三维的版本来运行,重新生成后声音播放没有问题,初步以为是我同事当天写的代码有问题,可是第二天,同样的问题又发生了,还是那样,三维有问题,仿真部分没问题,怎么回事呢?我将三维的声音线程写成和仿真部分一样的,还是三维出问题,仿真没问题.我不相信是动态库中出问题,同一个动态库,同样的代码,只是在两个不同的程序里,为什么一个出问题,而另一个不出问题呢?
&&&&&&&出错信息如下:
&&&&&&&&&&&&&&&& &windows已在PowipD.exe中触发一个断点.&&&&&&&&&&&&&&&&& 其原因可能是堆被损坏,这也说明PowipD.exe中或它所加载的任何DLL中有bug.&&&&&&&&&&&&&&&&&&输出窗口可能提供了更多诊断信息
&&&&&&&按下中断后输入窗口出现的信息如下:
&&&&&&&&&&&&&& HEAP[PowipD.exe]:Invalid Address specified to RtlValidateHeap( 01CC58 )&&&&&&&&&&&&&& Windows已在PowipD.exe中触发一个断点.&&&&&&&&&&&&&& 输出窗口可能提供了更多诊断信息
&&&&&&&由于这些原因让我迷惑不解,到底是为什么出现这种奇怪的事情呢?问题出在那里?
问题的解决:
简单地说:DLL中分配的内存DLL要负责释放!(一个模块分配的内存要在同一个模块中释放!)
找到解决方法后我查看了三个工程的设置(三个工程皆用VS2005开发):
&&&&&&&三维:使用MDd(多线程调试DLL)运行期库
&&&&&&&仿真控制:使用MTd(多线程调试)运行期库
&&&&&&&声音动态库:使用MDd(多线程调试DLL)运行期库
于是,我将三个工程皆改为:使用MDd(多线程调试DLL)运行期库时,问题解决。
但是如果三个工程中有的不是用C/C++写的,或者其中有工程的设置已经不便更改了,那又有什么办法呢?
该问题主要是关于DLL与进程的地址空间的问题(下面是&&核心编程&&中的一段话):
单个地址空间是由一个可执行模块和若干个DLL模块组成,这些模块中,有些可以链接到静态版本的C/C++运行期库, 有些可以链接到一个DLL版本的运行期库,而有些模块(如果不是用C/C++编写的话)则根本不需要C/C++运行期库,许多开发人员经常会犯一个常见的错误,因为他们忘记了若干个C/C++运行期库可以存在于单个地址空间中.请看下面的代码(下面代码不是书上的):
DLL中如下:
&&&&&& int *DllFunc()
&&&&&& int *p =
EXE中如下:
void EXEFunc()
&&&&&& int *p = DllFunc();
&&&&&& if( p!= NULL )
&&&&&&&&&&&&&
如何看待这个问题呢?上面的代码能够正确运行吗?DLL函数分配的内存是由EXE的函数释放的吗?答案是可能的.上面显示的代码并没有提供足够的信息.如果EXE和DLL都链接到DLL的C/C++运行期库,那么上面的代码将能够很好地运行.但是,如果两个模块中的一个或者两个链接到静态C/C++运行期库,那个对delete的操作就会失败.
一个很方便的方法可以解决这个问题.当一个模块提供一个用于分配内存块的函数时,该模块也必须提供相应的释放内存的函数,将上面的代码改成如下的样子就不会出错了:
DLL中如下:
&&&&&& int *DllFunc()
&&&&&& int *p =
void DLLDelete( int *p)
&&&&&& if( p != NULL )
&&&&&&&&&&&&&
EXE中如下:
void EXEFunc()
&&&&&& int *p = DllFunc();
&&&&&& DLLDelete( p );
这样的代码才是正确的,当我将代码改成如上类似后,再将三维的运行期库改为原来一样(使用MDd(多线程调试DLL)运行期库),三维的声音也正确无误地播放出来了。当你在编写一个模块时,你时刻都得记着,别人的代码不一定是用C/C++写的,也有可能不能同时链接到DLL的C/C++运行期库。malloc和free函数也有类似的问题。
当然在你在生成你的模块时是可以选择运行期库的,VC 6.0配有6个运行期库:其描述如下:
用于单线程应用程序的静态应用程序的静态链接库&&&&&&
用于单线程应用程序的静态链接库的调试版
LibCMt.lib
用于多线程应用程序的静态链接库的发行版
LibCMtD.lib
用于多线程应用程序的静态链接库的调试版
MSVCRt.lib
用于动态链接MSVCRt.dll库的发行版的输入库
MSVCRtD.lib
用于动态链接MSVCRtD.dll的调试版的输入库。该库同时支持单线程应用程序和多线程应用程序
而VS2005中只配了4个C/C++运行期库,就是上表中的后面4个。
建议各位在软件开发时同一软件的不同模块最好使用一致的运行库,否则还可能出现链接问题。
阅读(...) 评论()下次自动登录
现在的位置:
& 综合 & 正文
动态加载内存中DLL
一般分为隐式加载和显式加载两种,分别对应两种链接情况。本文主要讨论显式加载的技术问题。我们知道,要显式加载一个,并取得其中导出的函数地址一般是通过如下步骤:加载文件,获得该的模块句柄;取得该中目标函数的地址,赋值给函数指针变量;
文件位于硬盘上面。现在假设我们的已经位于内存中,比如通过脱壳、解密或者解压缩得到,能不能不把它写入硬盘文件,而直接从内存加 载呢?答案是肯定的。经过多天的研究,非法操作了次,修改了个,死亡了若干脑细胞后,终于有了初步的结果,下面做个总结与大家共享。
是把的代码映射到进程的虚拟地址空间中,我们要实现的也是这个。所以先 要弄清楚的文件结构。好在这个比较简单,它和一样也是文件结构,关于文件的资料很多,阅读一番后,基本上知道了必须做的几个工作:。这个功能通过函数完成。原型是:所需的虚拟内存大小。这个功能通过函数完成。原型是:数据复制到所分配的虚拟内存块中。该功能通过函数完成。要注意段对齐。完成。原型是:的引入地址表。这个功能由函数完成。原型是:每个节的属性设置其对应内存页的读写属性。我这里做了简化,所有内存区域都设置成一样的读写属性。,完成初始化工作。这一步我一开始忽略了,所以总是发现自己加载的和加载的有些不同我把整块内存区域保存到两个文件中进行比较,够晕的。只是最近猜想到还需要这一步。的基地址(即分配的内存块起始地址)用于查找的导出函数。从现在开始这个已经完全映射到了进程的虚拟地址空间,可以使用它了。的时候,释放所分配的虚拟内存。
仅仅针对动态库,没有考虑常规和扩展。中的函数,对于导出类的,由于通常都是隐式链接,所以也没有考虑。导出变量的虽然也是隐式链接,但是通过查找函数的方法也可 以找到该变量,不过在取值的时候一定要符合中对变量的定义,比如中导出的是一个变量,则得到该变量在中的地址后,需要强制转换成 指针,然后取值。导出的函数(或者变量)的名字。这里必须注意函数名修饰,通常不加的函数,编译以后在中导出的都是修饰名,比如:头文件中中的导出符号变成 中添加修饰。最好是给加一个文件,里面明确给出每个函数的导出名字。函数中没有考虑对操作系统版本的要求。等等。由于这个代码基于手头已有的文件资料,对于不熟悉的节,在映射数据的时候没有考虑是否需要处理。映射到进程的地址空间以后,我试图直接使用查找函数。最初我认为返回的 值是,把它传递给可以找到目标函数,而我也把映射到这个 地址,但是当我把这个值传递给的时候,发现无法找到函数,用得到错误码一看是无效句柄的错误,这才 明白原来在加载的时候,同时创建了一个句柄放入进程的句柄表,而我们要做这个工作是比较麻烦的,所以只能自己写一个查找函 数。所占据的虚拟内存,原来我使用
,工程的名字取
向导创建一个工程,里面选择导出一些符号,为了测试需要,对源代码进行如下修改:)头文件和 参数:)文件
工程,测试代码如下:
里面保存有文件的路径
到当前进程的地址空间
typedef BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD, LPVOID );class CMemLoadDll{
CMemLoadDll();
~CMemLoadDll();
BOOL MemLoadLibrary( void* lpFileData , int DataLength); // Dll file data buffer
FARPROC MemGetProcAddress(LPCSTR lpProcName);
BOOL isLoadOk;
BOOL CheckDataValide(void* lpFileData, int DataLength);
int CalcTotalImageSize();
void CopyDllDatas(void* pDest, void* pSrc);
BOOL FillRavAddress(void* pBase);
void DoRelocation(void* pNewBase);
int GetAlignedSize(int Origin, int Alignment);
ProcDllMain pDllM
DWORD pImageB
PIMAGE_DOS_HEADER pDosH
PIMAGE_NT_HEADERS pNTH
PIMAGE_SECTION_HEADER pSectionH};CMemLoadDll::CMemLoadDll(){
isLoadOk = FALSE;
pImageBase = NULL;
pDllMain = NULL;}CMemLoadDll::~CMemLoadDll(){
if(isLoadOk)
ASSERT(pImageBase != NULL);
ASSERT(pDllMain != NULL);
//脱钩,准备卸载dll
pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);
VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
}}//MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间,缺省位置0×//返回值: 成功返回TRUE , 失败返回FALSE//lpFileData: 存放dll文件数据的缓冲区//DataLength: 缓冲区中数据的总长度BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength){
if(pImageBase != NULL)
return FALSE; //已经加载一个dll,还没有释放,不能加载新的dll
}//检查数据有效性,并初始化
if(!CheckDataValide(lpFileData, DataLength))return FALSE;
//计算所需的加载空间
int ImageSize = CalcTotalImageSize();
if(ImageSize == 0) return FALSE;
// 分配虚拟内存
void *pMemoryAddress = VirtualAlloc((LPVOID)0×, ImageSize,
MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pMemoryAddress == NULL)
return FALSE;
CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据,并对齐每个段
//重定位信息
if(pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress &0
&& pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size&0)
DoRelocation(pMemoryAddress);
//填充引入地址表
if(!FillRavAddress(pMemoryAddress)) //修正引入地址表失败
VirtualFree(pMemoryAddress,0,MEM_RELEASE);
return FALSE;
//修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。
//统一设置成一个属性PAGE_EXECUTE_READWRITE
VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&old);
//修正基地址
pNTHeader-&OptionalHeader.ImageBase = (DWORD)pMemoryA
//接下来要调用一下dll的入口函数,做初始化工作。
pDllMain = (ProcDllMain)(pNTHeader-&OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);
BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);
if(!InitResult) //初始化失败
pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);
VirtualFree(pMemoryAddress,0,MEM_RELEASE);
pDllMain = NULL;
return FALSE;
isLoadOk = TRUE;
pImageBase = (DWORD)pMemoryA
return TRUE;}//MemGetProcAddress函数从dll中获取指定函数的地址//返回值: 成功返回函数地址 , 失败返回NULL//lpProcName: 要查找函数的名字或者序号FARPROC CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName){
if(pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||
pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)
return NULL;
if(!isLoadOk) return NULL;
DWORD OffsetStart = pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualA
DWORD Size = pNTHeader-&OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].S</span
【上篇】【下篇】全面介绍Windows内存管理机制及C++内存分配实例
原文地址:
本文基本上是windows via c/c&#43;&#43;上的内容,笔记做得不错。。
本文背景:
在编程中,很多Windows或C&#43;&#43;的内存函数不知道有什么区别,更别谈有效使用;根本的原因是,没有清楚的理解的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制。
本文目的:
对Windows内存管理机制了解清楚,有效的利用C&#43;&#43;内存函数管理和使用内存。
1.&&&&&&进程地址空间
1.1地址空间
·&&&&&&&&32|64位的系统|CPU
&&&&&&&&操作系统运行在硬件CPU上,32位操作系统运行于32位CPU上,64位操作系统运行于64位CPU上;目前没有真正的64位CPU。
32位CPU一次只能操作32位二进制数;位数多CPU设计越复杂,软件设计越简单。
&&&&&&&软件的进程运行于32位系统上,其寻址位也是32位,能表示的空间是232=4G,范围从0xxFFFF FFFF。
·&&&&&&&&NULL指针分区
范围:0xx0000 FFFF
作用:保护内存非法访问
例子:分配内存时,如果由于某种原因分配不成功,则返回空指针0x;当用户继续使用比如改写数据时,系统将因为发生访问违规而退出。
&&&&&&&&那么,为什么需要那么大的区域呢,一个地址&#20540;不就行了吗?我在想,是不是因为不让8或16位的程序运行于32位的系统上呢?!因为NULL分区刚好范围是16的进程空间。
·&&&&&&&&独享用户分区
范围:0xx7FFE FFFF
作用:进程只能读取或访问这个范围的虚拟地址;超越这个范围的行为都会产生违规退出。
&&&&&&&&程序的二进制代码中所用的地址大部分将在这个范围,所有exe和dll文件都加载到这个。每个进程将近2G的空间是独享的。
注意:如果在boot.ini上设置了/3G,这个区域的范围从2G扩大为3G:0xxBFFE FFFF。
·&&&&&&&&共享内核分区
范围:0xxFFFF FFFF
作用:这个空间是供操作系统内核代码、设备驱动程序、设备I/O高速缓存、非页面内存池的分配、进程目表和页表等。
&&&&&&&这段地址各进程是可以共享的。&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
注意:如果在boot.ini上设置了/3G,这个区域的范围从2G缩小为1G:0xC000 0000~0xFFFF FFFF。
&&&&&&&通过以上分析,可以知道,如果系统有n个进程,它所需的虚拟空间是:2G*n&#43;2G (内核只需2G的共享空间)。
1.2地址映射
·&&&&&&&&区域
区域指的是上述地址空间中的一片连续地址。区域的大小必须是粒度(64k)&的整数倍,不是的话系统自动处理成整数倍。不同CPU粒度大小是不一样的,大部分都是64K。
区域的状态有:空闲、私有、映射、映像。
在你的应用程序中,申请空间的过程称作保留(预订),可以用VirtualAlloc;删除空间的过程为释放,可以用VirtualFree。
&&&&&&&&在程序里预订了地址空间以后,你还不可以存取数据,因为你还没有付钱,没有真实的RAM和它关联。
这时候的区域状态是私有;
默认情况下,区域状态是空闲;
当exe或DLL文件被映射进了进程空间后,区域状态变成映像;
当一般数据文件被映射进了进程空间后,区域状态变成映射。
·&&&&&&&&物理存储器
Windows各系列支持的内存上限是不一样的,从2G到64G不等。理论上32位CPU,硬件上只能支持4G内存的寻址;能支持超过4G的内存只能靠其他技术来弥补。顺便提一下,Windows个人版只能支持最大2G内存,Intel使用Address Windows Extension (AWE)&技术使得寻址范围为236=64G。当然,也得操作系统配合。
&&&&&&&&内存分配的最小单位是4K或8K,一般来说,根据CPU不同而不同,后面你可以看到可以通过系统函数得到区域粒度和页面粒度。
·&&&&&&&&页文件
页文件是存在硬盘上的系统文件,它的大小可以在系统属性里面设置,它相当于物理内存,所以称为虚拟内存。事实上,它的大小是影响系统快慢的关键所在,如果物理内存不多的情况下。
&&&&&&&每页的大小和上述所说内存分配的最小单位是一样的,通常是4K或8K。
·&&&&&&&&访问属性
物理页面的访问属性指的是对页面进行的具体操作:可读、可写、可执行。CPU一般不支持可执行,它认为可读就是可执行。但是,操作系统提供这个可执行的权限。
PAGE_NOACCESS
PAGE_READONLY
PAGE_READWRITE
PAGE_EXECUTE
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE
这6个属性很好理解,第一个是拒绝所有操作,最后一个是接受收有操作;
PAGE_WRITECOPY
PAGE_EXECUTE_WRITECOPY
这两个属性在运行同一个程序的多个实例时非常有用;它使得程序可以共享代码段和数据段。一般情况下,多个进程只读或执行页面,如果要写的话,将会Copy页面到新的页面。通过映射exe文件时设置这两个属性可以达到这个目的。
PAGE_NOCACHE
PAGE_WRITECOMBINE
这两个是开发设备驱动的时候需要的。
PAGE_GUARD
当往页面写入一个字节时,应用程序会收到堆栈溢出通知,在线程堆栈时有用。
·&&&&&&&&映射过程
进程地址空间的地址是虚拟地址,也就是说,当取到指令时,需要把虚拟地址转化为物理地址才能够存取数据。这个工作通过页目和页表进行。
从图中可以看出,页目大小为4K,其中每一项(32位)保存一个页表的物理地址;每个页表大小为4K,其中每一项(32位)保存一个物理页的物理地址,一共有1024个页表。利用这4K&#43;4K*1K=4.4M的空间可以表示进程的* (一页4K) =4G的地址空间。
进程空间中的32位地址如下:
高10位用来找到1024个页目项中的一项,取出页表的物理地址后,利用中10位来得到页表项的&#20540;,根据这个&#20540;得到物理页的地址,由于一页有4K大小,利用低12位得到单元地址,这样就可以访问这个内存单元了。
&&&&&&&&每个进程都有自己的一个页目和页表,那么,刚开始进程是怎么找到页目所在的物理页呢?答案是CPU的CR3寄存器会保存当前进程的页目物理地址。
&&&&&&&&当进程被创建时,同时需要创建页目和页表,一共需要4.4M。在进程的空间中,0xC030 0 0FFF是用来保存页目的4k空间。0xC000 F FFFF是用来保存页表的4M空间。也就是说程序里面访问这些地址你是可以读取页目和页表的具体&#20540;的(要工作在内核方式下)。有一点我不明白的是,页表的空间包含了页目的空间!
&&&&&&&&至于说,页目和页表是保存在物理内存还是页文件中,我觉得,页目比较常用,应该在物理内存的概率大点,页表需要时再从页文件导入物理内存中。
&&&&&&&&页目项和页表项是一个32位的&#20540;,当页目项第0位为1时,表明页表已经在物理内存中;当页表项第0位为1时,表明访问的数据已经在内存中。还有很多数据是否已经被改变,是否可读写等标志。另外,当页目项第7位为1时,表明这是一个4M的页面,这&#20540;已经是物理页地址,用虚拟地址的低22位作为偏移量。还有很多:数据是否已经被改变、是否可读写等标志。
1.3&一个例子
·&&&&&&&&编写生成软件程序exe
软件描述如下:
1:定义全局变量
2:处理函数逻辑(Load&所需DLL库,调用方法处理逻辑)
3:定义并实现各种方法(方法含有局部变量)
&&&&&&&&&&&&&&&&&&&&&&&4:程序结束
将程序编译,生成exe文件,附带所需的DLL库。
·&&&&&&&&exe文件&#26684;式
exe文件有自己的&#26684;式,有若干节(section):.text用来放二进制代码(exe或dll);.data用来放各种全局数据。
指令1:move a, b
指令2:add a, b
数据1:a=2
数据2:b=1
这些地址都是虚拟地址,也就是进程的地址空间。
·&&&&&&&&运行exe程序
建立进程:运行这个exe程序时,系统会创建一个进程,建立进程控制块PCB,生成进程页目和页表,放到PCB中。
数据对齐:数据的内存地址除以数据的大小,余数为0时说明数据是对齐的。现在的编译器编译时就考虑数据对齐的问题,生成exe文件后,数据基本上是对齐的,CPU运行时,寄存器有标志标识CPU是否能够自动对齐数据,如果遇到不能对齐的情况,或者通过两次访问内存,或者通知操作系统处理。
要注意的是,如果数据没有对齐,CPU处理的效率是很低的。
文件映射:系统不会将整个exe文件和所有的DLL文件装载进物理内存中,同时它也不会装载进页面文件中。相反,它会建立文件映射,也就是利用exe本身当作页面文件。系统将部分二进制代码装载进内存,分配页面给它。
&&&&&&&&假设分配了一个页面,物理地址为0x0232 FFF1。其中装载的一个指令虚拟地址为0x=&&01。一个页面有4K,系统会将指令保存在低12位0x0001的地址处。同时,系统根据高10位0x0100找到页目项,如果没有关联的页表,系统会生成一个页表,分配一个物理页;然后,根据中10位0x0001找到表项,将物理地址0x0232
FFF1存进去。
执行过程:
执行时,当系统拿到一个虚拟地址,就根据页目和页表找到数据的地址,根据页目上的&#20540;可以判断页表是在页文件中还是在内存中;
如果在页文件中,会将页面导入内存,更新页目项。读取页表项的&#20540;后,可以判断数据页文件中还是在物理内存中;如果在页文件中,会导入到内存中,更新页表项。最终,拿到了数据。
&&&&&&&&在分配物理页的过程中,系统会根据内存分配的状况适当淘汰暂时不用的页面,如果页面内容改变了(通过页表项的标志位),保存到页文件中,系统会维护内存与页文件的对应关系。
由于将exe文件当作内存映射文件,当需要改变数据,如更改全局变量的&#20540;时,利用Copy-On-Write的机制,重新生成页文件,将结果保存在这个页文件中,原来的页文件还是需要被其他进程实例使用的。
&&&&&&&&在清楚了指令和数据是如何导入内存,如何找到它们的情况下,剩下的就是CPU不断的取指令、运行、保存数据的过程了,当进程结束后,系统会清空之前的各种结构、释放相关的物理内存和删除页文件。& &
2.&&&&&&内存状态查询函数
2.1系统信息
Windows&提供API可以查询系统内存的一些属性,有时候我们需要获取一些页面大小、分配粒度等属性,在分配内存时用的上。
请看以下C&#43;&#43;程序:
SYSTEM_INFO sysI
&&&&&&&&&&&&GetSystemInfo(&sysInfo);
&&&&&&&&&&&&cout&&&机器属性:&&&
&&&&&&&&&&&&cout&&&页大小=&&&sysInfo.dwPageSize&&
&&&&&&&&&&&&cout&&&分配粒度=&&&sysInfo.dwAllocationGranularity&&
&&&&&&&&&&&&cout&&&用户区最小&#20540;=&&&sysInfo.lpMinimumApplicationAddress&&
&&&cout&&&用户区最大&#20540;=&
&&sysInfo.lpMaximumApplicationAddress&&endl&&
结果如下:
可以看出,页面大小是4K,区域分配粒度是64K,进程用户区是0xx7FFE FFFF。
2.2内存状态
·&&&&&&&&内存状态可以获取总内存和可用内存,包括页文件和物理内存。
请看以下C&#43;&#43;程序:
MEMORYSTATUS memS
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus);
&&&&&&&&&&&&cout&&&内存初始状态:&&&
&&&&&&&&&&&&cout&&&内存繁忙程度=&&&memStatus.dwMemoryLoad&&
&&&&&&&&&&&&cout&&&总物理内存=&&&memStatus.dwTotalPhys&&
&&&&&&&&&&&&cout&&&可用物理内存=&&&memStatus.dwAvailPhys&&
&&&&&&&&&&&&cout&&&总页文件=&&&memStatus.dwTotalPageFile&&
&&&&&&&&&&&&cout&&&可用页文件=&&&memStatus.dwAvailPageFile&&
&&&&&&&&&&&&cout&&&总进程空间=&&&memStatus.dwTotalVirtual&&
&&&cout&&&可用进程空间=&&&memStatus.dwAvailVirtual&&endl&&
结果如下:
可以看出,总物理内存是1G,可用物理内存是510兆,总页文件是2.5G,这个是包含物理内存的页文件;可用页文件是1.9G。这里还标识了总进程空间,还有可用的进程空间,程序只用了22兆的内存空间。这里说的都是大约数。
内存繁忙程序是标识当前系统内存管理的繁忙程序,从0到100,其实用处不大。
·&&&&&&&&在函数里面静态分配一些内存后,看看究竟发生什么
char&stat[65536];
&&&&&&&&&&&&MEMORYSTATUS memStatus1;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus1);
&&&&&&&&&&&&cout&&&静态分配空间:&&&
&&&&&&&&&&&&printf(&指针地址=%x/n&,stat);
cout&&&减少物理内存=&&&memStatus.dwAvailPhys-memStatus1.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus.dwAvailPageFile-memStatus1.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatus.dwAvailVirtual-&&&&&&&&&&&&&&
memSta tus1.dwAvailVirtual&&endl&&
结果如下:
可以看出,物理内存、可用页文件和进程空间都没有损耗。因为局部变量是分配在线程堆栈里面的,每个线程系统都会建立一个默认1M大小的堆栈给线程函数调用使用。如果分配超过1M,就会出现堆栈溢出。
·&&&&&&&&在函数里面动态分配300M的内存后,看看究竟发生什么
char&*dynamic=new&char[300*];
&&&&&&&&&&&&MEMORYSTATUS memStatus2;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus2);
&&&&&&&&&&&&cout&&&动态分配空间:&&&
&&&&&&&&&&&&printf(&指针地址=%x/n&,dynamic);
cout&&&减少物理内存=&&&memStatus.dwAvailPhys-memStatus2.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus.dwAvailPageFile-memStatus2.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatus.dwAvailVirtual-memStatus2.dwAvailVirtual&&endl&&
结果如下:
动态分配情况下,系统分配直到内存页文件使用完为止,当然,系统要留一下系统使用的页面。
2.3&进程区域地址查询
在给定一个进程空间的地址后,可以查询它所在区域和相邻页面的状态,包括页面保护属性、存储器类型等。
·&&&&&&&&C&#43;&#43;静态分配了两次内存,一次是4K大一点,一个是900K左右。
char&arrayA[4097];
&&&&&&&&&&&&char&arrayB[900000];
第一次查询:
&&&&&&&&&&&&long&len=sizeof(MEMORY_BASIC_INFORMATION);
&&&&&&&&&&&&MEMORY_BASIC_INFORMATION mbiA;
&&&&&&&&&&&&VirtualQuery(arrayA,&mbiA,len);
&&&&&&&&&&&&cout&&&静态内存地址属性:&&&
&&&&&&&&&&&&cout&&&区域基地址=&&&mbiA.AllocationBase&&
&&&&&&&&&&&&cout&&&区域邻近页面状态=&&&mbiA.State&&
&&&&&&&&&&&&cout&&&区域保护属性=&&&mbiA.AllocationProtect&&
&&&&&&&&&&&&cout&&&页面基地址=&&&mbiA.BaseAddress&&
&&&&&&&&&&&&printf(&arrayA指针地址=%x/n&,arrayA);
&&&&&&&&&&&&cout&&&从页面基地址开始的大小=&&&mbiA.RegionSize&&
&&&&&&&&&&&&cout&&&邻近页面物理存储器类型=&&&mbiA.Type&&
&&&&&&&&&&&&cout&&&页面保护属性=&&&mbiA.Protect&&endl&&
第二次查询:
&&&&&&&&&&&&MEMORY_BASIC_INFORMATION mbiB;
&&&&&&&&&&&&VirtualQuery(arrayB,&mbiB,len);
&&&&&&&&&&&&cout&&&静态内存地址属性:&&&
&&&&&&&&&&&&cout&&&区域基地址=&&&mbiB.AllocationBase&&
&&&&&&&&&&&&cout&&&区域邻近页面状态=&&&mbiB.State&&
&&&&&&&&&&&&cout&&&区域保护属性=&&&mbiB.AllocationProtect&&
&&&&&&&&&&&&cout&&&页面基地址=&&&mbiB.BaseAddress&&
&&&&&&&&&&&&printf(&arrayB指针地址=%x/n&,arrayB);
&&&&&&&&&&&&cout&&&从页面基地址开始的大小=&&&mbiB.RegionSize&&
&&&&&&&&&&&&cout&&&邻近页面物理存储器类型=&&&mbiB.Type&&
&&&cout&&&页面保护属性=&&&mbiB.Protect&&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映像=)。
页面保护属性指的是页面被指定的保护属性,在区域保护属性指定后更新。
结果如下:
如前所说,这是在堆栈区域0x里分配的,后分配的地址arrayB反而更小,符合堆栈的特性。arrayA和arrayB它们处于不同的页面。页面都受页文件支持,并且区域都是提交的,是系统在线程创建时提交的。
·&&&&&&&&C&#43;&#43;动态分配了两次内存,一次是1K大一点,一个是64K左右。所以应该不会在一个区域。
char&*dynamicA=new&char[1024];
&&&&&&&&&&&&char&*dynamicB=new&char[65467];
&&&&&&&&&&&&VirtualQuery(dynamicA,&mbiA,len);
&&&&&&&&&&&&cout&&&动态内存地址属性:&&&
&&&&&&&&&&&&cout&&&区域基地址=&&&mbiA.AllocationBase&&
&&&&&&&&&&&&cout&&&区域邻近页面状态=&&&mbiA.State&&
&&&&&&&&&&&&cout&&&区域保护属性=&&&mbiA.AllocationProtect&&
&&&&&&&&&&&&cout&&&页面基地址=&&&mbiA.BaseAddress&&
&&&&&&&&&&&&printf(&dynamicA指针地址=%x/n&,dynamicA);
&&&&&&&&&&&&cout&&&从页面基地址开始的大小=&&&mbiA.RegionSize&&
&&&&&&&&&&&&cout&&&邻近页面物理存储器类型=&&&mbiA.Type&&
&&&&&&&&&&&&cout&&&页面保护属性=&&&mbiA.Protect&&endl&&
&&&&&&&&&&&&VirtualQuery(dynamicB,&mbiB,len);
&&&&&&&&&&&&cout&&&动态内存地址属性:&&&
&&&&&&&&&&&&cout&&&区域基地址=&&&mbiB.AllocationBase&&
&&&&&&&&&&&&cout&&&区域邻近页面状态=&&&mbiB.State&&
&&&&&&&&&&&&cout&&&区域保护属性=&&&mbiB.AllocationProtect&&
&&&&&&&&&&&&cout&&&页面基地址=&&&mbiB.BaseAddress&&
&&&&&&&&&&&&printf(&dynamicB指针地址=%x/n&,dynamicB);
&&&&&&&&&&&&cout&&&从页面基地址开始的大小=&&&mbiB.RegionSize&&
&&&&&&&&&&&&cout&&&邻近页面物理存储器类型=&&&mbiB.Type&&
&&&&&&&&&&&&cout&&&页面保护属性=&&&mbiB.Protect&&
结果如下:
这里是动态分配,dynamicA和dynamicB处于两个不同的区域;同样,页面都受页文件支持,并且区域都是提交的。
第二个区域是比64K大的,由分配粒度可知,区域至少是128K。那么,剩下的空间也是提交的吗,如果是的话那就太浪费了。看看就知道了:0x00E2 1000肯定在这个空间里,所以查询如下:
VirtualQuery((char*)0xE23390,&mbiB,len);
&&&&&&&&&&&&cout&&&动态内存地址属性:&&&
&&&&&&&&&&&&cout&&&区域基地址=&&&mbiB.AllocationBase&&
&&&&&&&&&&&&cout&&&区域邻近页面状态=&&&mbiB.State&&
&&&&&&&&&&&&cout&&&区域保护属性=&&&mbiB.AllocationProtect&&
&&&&&&&&&&&&cout&&&页面基地址=&&&mbiB.BaseAddress&&
&&&&&&&&&&&&printf(&dynamicB指针地址=%x/n&,0xE21000);
&&&&&&&&&&&&cout&&&从页面基地址开始的大小=&&&mbiB.RegionSize&&
&&&&&&&&&&&&cout&&&邻近页面物理存储器类型=&&&mbiB.Type&&
&&&cout&&&页面保护属性=&&&mbiB.Protect&&
结果如下:
可以看出,邻近页面状态为保留,还没提交,预料之中;0x00E1 0000&这个区域的大小可以计算出来:69632&#43;978944=1024K。系统动态分配了1M的空间,就为了64K左右大小的空间。可能是为了使得下次有要求分配时时不用再分配了。
<span style="color:#.&&&&&&内存管理机制--虚拟内存&(VM)
·&&&&&&&&虚拟内存使用场合
虚拟内存最适合用来管理大型对象或。比如说,电子表&#26684;程序,有很多单元&#26684;,但是也许大多数的单元&#26684;是没有数据的,用不着分配空间。也许,你会想到用动态链表,但是访问又没有数组快。定义二维数组,就会浪费很多空间。
它的优点是同时具有数组的快速和链表的小空间的优点。
·&&&&&&&&分配虚拟内存
如果你程序需要大块内存,你可以先保留内存,需要的时候再提交物理存储器。在需要的时候再提交才能有效的利用内存。一般来说,如果需要内存大于1M,用虚拟内存比较好。
·&&&&&&&&保留
用以下Windows&函数保留内存块
VirtualAlloc (PVOID&开始地址,SIZE_T&大小,DWORD&类型,DWORD&保护属性)
一般情况下,你不需要指定“开始地址”,因为你不知道进程的那段空间是不是已经被占用了;所以你可以用NULL。“大小”是你需要的内存字节;“类型”有MEM_RESERVE(保留)、MEM_RELEASE(释放)和MEM_COMMIT(提交)。“保护属性”在前面章节有详细介绍,只能用前六种属性。
如果你要保留的是长久不会释放的内存区,就保留在较高的空间区域,这样不会产生碎片。用这个类型标志可以达到:
MEM_RESERVE|MEM_TOP_DOWN。
C&#43;&#43;程序:保留1G的空间
LPVOID pV=VirtualAlloc(NULL,24,MEM_RESERVE|MEM_TOP_DOWN,PAGE_READWRITE);&
&&&&&&&&&&&&if(pV==NULL)
&&&&&&&&&&&&cout&&&没有那么多虚拟空间!&&&
&&&&&&&&&&&&MEMORYSTATUS memStatusVirtual1;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatusVirtual1);
&&&&&&&&&&&&cout&&&虚拟内存分配:&&&
&&&&&&&&&&&&printf(&指针地址=%x/n&,pV);
cout&&&减少物理内存=&&&memStatusVirtual.dwAvailPhys-memStatusVirtual1.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatusVirtual.dwAvailPageFile-memStatusVirtual1.dwAvailPageFile&&
cout&&&减少可用进程空间=&
&&memStatusVirtual.dwAvailVirtual-memStatusVirtual1.dwAvailVirtual&&endl&&
结果如下:
可见,进程空间减少了1G;减少的物理内存和可用页文件用来管理页目和页表。但是,现在访问空间的话,会出错的:
int&* iV=(int*)pV;
&&&//iV[0]=1;现在访问会出错,出现访问违规
·&&&&&&&&提交
你必须提供一个初始地址和提交的大小。提交的大小系统会变成页面的倍数,因为只能按页面提交。指定类型是MEM_COMMIT。保护属性最好跟区域的保护属性一致,这样可以提高系统管理的效率。
C&#43;&#43;程序:提交100M的空间
LPVOID pP=VirtualAlloc(pV,100*,MEM_COMMIT,PAGE_READWRITE);&&&&
&&&&&&&&&&&&if(pP==NULL)
&&&&&&&&&&&&cout&&&没有那么多物理空间!&&&
&&&&&&&&&&&&int&* iP=(int*)pP;
&&&&&&&&&&&&iP[0]=3;
&&&&&&&&&&&&iP[100/sizeof(int)*]=5;//这是能访问的最后一个地址
&&&&&&&&&&&&//iP[100/sizeof(int)*]=5;访问出错
·&&&&&&&&保留&提交
你可以用类型MEM_RESERVE|MEM_COMMIT一次全部提交。但是这样的话,没有有效地利用内存,和使用一般的C&#43;&#43;动态分配内存函数一样了。
·&&&&&&&&更改保护属性
更改已经提交的页面的保护属性,有时候会很有用处,假设你在访问数据后,不想别的函数再访问,或者出于防止指针乱指改变结构的目的,你可以更改数据所处的页面的属性,让别人无法访问。
VirtualProtect (PVOID&基地址,SIZE_T&大小,DWORD&新属性,DWORD&旧属性)
“基地址”是你想改变的页面的地址,注意,不能跨区改变。
C&#43;&#43;程序:更改一页的页面属性,改为只读,看看还能不能访问
&&&&&&&&&&&&iP[0]=8;
&&&&&&&&&&&&VirtualProtect(pV,4096,PAGE_READONLY,&protect);
&&&&&&&&&&&&int&* iP=(int*)pV;
iP[1024]=9;//可以访问,因为在那一页之外
&&&&&&&&&&&&//iP[0]=9;不可以访问,只读
&&&&&&&&&&&&//还原保护属性
&&&&&&&&&&&&VirtualProtect(pV,4096,PAGE_READWRITE,&protect);
&&&cout&&&初始&#20540;=&&&iP[0]&&//可以访问
·&&&&&&&&清除物理存储器内容
清除页面指的是,将页面清零,也就是说当作页面没有改变。假设数据存在物理内存中,系统没有RAM页面后,会将这个页面暂时写进虚拟内存页文件中,这样来回的倒腾系统会很慢;如果那一页数据已经不需要的话,系统可以直接使用。当程序需要它那一页时,系统会分配另一页给它。
VirtualAlloc (PVOID&开始地址,SIZE_T&大小,DWORD&类型,DWORD&保护属性)
“大小”如果小于一个页面的话,函数会执行失败,因为系统使用四舍五入的方法;“类型”是MEM_RESET。
有人说,为什么需要清除呢,释放不就行了吗?你要知道,释放了后,程序就无法访问了。现在只是因为不需要结构的内容了,顺便提高一下系统的性能;之后程序仍然需要访问这个结构的。
C&#43;&#43;程序:
清除1M的页面:
PVOID re=VirtualAlloc(pV,,MEM_RESET,PAGE_READWRITE);
&&&&&&&&&&&&if(re==NULL)
&&&cout&&&清除失败!&&&
这时候,页面可能还没有被清零,因为如果系统没有RAM请求的话,页面内存保存不变的,为了看看被清零的效果,程序人为的请求大量页面:
C&#43;&#43;程序:
VirtualAlloc((char*)pV&#43;100*&#43;4096,memStatus.dwAvailPhys&#43;,MEM_COMMIT,PAGE_READWRITE);//没访问之前是不给物理内存的。&&&
&&&&&&&&&&&&char* pp=(char*)pV&#43;100*&#43;4096;
&&&&&&&&&&&&for(int&i=0;i&memStatus.dwAvailPhys&#43;;i&#43;&#43;)
&&&&&&&&&&&&pp[i]='V';//&#36924;他使用物理内存,而不使用页文件
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus);
&&&&&&&&&&&&cout&&&内存初始状态:&&&
&&&&&&&&&&&&cout&&&长度=&&&memStatus.dwLength&&
&&&&&&&&&&&&cout&&&内存繁忙程度=&&&memStatus.dwMemoryLoad&&
&&&&&&&&&&&&cout&&&总物理内存=&&&memStatus.dwTotalPhys&&
&&&&&&&&&&&&cout&&&可用物理内存=&&&memStatus.dwAvailPhys&&
&&&&&&&&&&&&cout&&&总页文件=&&&memStatus.dwTotalPageFile&&
&&&&&&&&&&&&cout&&&可用页文件=&&&memStatus.dwAvailPageFile&&
&&&&&&&&&&&&cout&&&总进程空间=&&&memStatus.dwTotalVirtual&&
&&&&&&&&&&&&cout&&&可用进程空间=&&&memStatus.dwAvailVirtual&&
&&&cout&&&清除后=&&&iP[0]&&
当内存所剩无几时,系统将刚清除的内存页面分配出去,同时不会把页面的内存写到虚拟页面文件中。可以看见,原先是8的&#20540;现在是0了。
·&&&&&&&&虚拟内存的关键之处
虚拟内存存在的优点是,需要的时候才真正分配内存。那么程序必须决定何时才提交内存。
如果访问没有提交内存的数据结构,系统会产生访问违规的错误。提交的最好方法是,当你程序需要访问虚拟内存的数据结构时,假设它已经是分配内存的,然后异常处理可能出现的错误。对于访问违规的错误,就提交这个地址的内存。
·&&&&&&&&释放
可以释放整个保留的空间,或者只释放分配的一些物理内存。
释放特定分配的物理内存:
如果不想释放所有空间,可以只释放某些物理内存。
“开始地址”是页面的基地址,这个地址不一定是第一页的地址,一个窍门是提供一页中的某个地址就行了,因为系统会做页边界处理,取该页的首地址;“大小”是页面的要释放的字节数;“类型”是MEM_DECOMMIT。
C&#43;&#43;程序:
&&&&&&&&&&&&//只释放物理内存
&&&&&&&&&&&&VirtualFree((int*)pV&#43;4*1024,MEM_DECOMMIT);
&&&&&&&&&&&&int* a=(int*)pV;
&&&&&&&&&&&&a[10]=2;//可以使用,没有释放这一页
&&&&&&&&&&&&MEMORYSTATUS memStatusVirtual3;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatusVirtual3);
&&&&&&&&&&&&cout&&&物理内存释放:&&&
cout&&&增加物理内存=&&&memStatusVirtual3.dwAvailPhys-memStatusVirtual2.dwAvailPhys&&
cout&&&增加可用页文件=&&&memStatusVirtual3.dwAvailPageFile-memStatusVirtual2.dwAvailPageFile&&
&&&cout&&&增加可用进程空间=&
&&memStatusVirtual3.dwAvailVirtual-memStatusVirtual2.dwAvailVirtual&&endl&&
结果如下:
可以看见,只释放物理内存,没有释放进程的空间。
释放整个保留的空间:
VirtualFree (LPVOID&开始地址,SIZE_T&大小,DWORD&类型)
“开始地址”一定是该区域的基地址;“大小”必须是0,因为只能释放整个保留的空间;“类型”是MEM_RELEASE。
C&#43;&#43;程序:
VirtualFree(pV,0,MEM_RELEASE);
&&&&&&&&&&&&//a[10]=2;不能使用了,进程空间也释放了
&&&&&&&&&&&&MEMORYSTATUS memStatusVirtual4;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatusVirtual4);
&&&&&&&&&&&&cout&&&虚拟内存释放:&&&
cout&&&增加物理内存=&&&memStatusVirtual4.dwAvailPhys-memStatusVirtual3.dwAvailPhys &&
cout&&&增加可用页文件=&&&memStatusVirtual4.dwAvailPageFile-memStatusVirtual3.dwAvailPageFile&&
cout&&&增加可用进程空间=&
&&memStatusVirtual4.dwAvailVirtual-memStatusVirtual3.dwAvailVirtual&&endl&&
结果如下:
整个分配的进程区域被释放了,包括所占的物理内存和页文件。
·&&&&&&&&何时释放
如果数组的元素大小是小于一个页面4K的话,你需要记录哪些空间不需要,哪些在一个页面上,可以用一个元素一个Bit来记录;另外,你可以创建一个线程定时检测无用单元。
·&&&&&&&&扩展地址AWE
AWE是内存管理器功能的一套应用程序编程接口&(API)&,它使程序能够将物理内存保留为非分页内存,然后将非分页内存部分动态映射到程序的内存工作集。此过程使内存密集型程序(如大型系统)能够为数据保留大量的物理内存,而不必交换分页文件以供使用。相反,数据在工作集中进行交换,并且保留的内存超过&4
对于物理内存小于2G进程空间时,它的作用是:不必要在物理内存和虚拟页文件中交换。
对于物理内存大于2G进程空间时,它的作用是:应用程序能够访问的物理内存大于2G,也就相当于进程空间超越了2G的范围;同时具有上述优点。
当在boot.ini&上加上&/3GB&选项时,应用程序的进程空间增加了1G,也就是说,你写程序时,可以分配的空间又增大了1G,而不管物理内存是多少,反正有虚拟内存的页文件,大不了慢点。
当在boot.ini上加上&/PAE&选项时,操作系统可以支持大于4G的物理内存,否则,你加再多内存操作系统也是不认的,因为管理这么大的内存需要特殊处理。所以,你内存小于4G是没有必要加这个选项的。注意,当要支持大于16G的物理内存时,不能使用/3G选项,因为,只有1G的系统空间是不能管理超过16G的内存的。
当在boot.ini上加上&/AWE选项时,应用程序可以为自己保留物理内存,直接的使用物理内存而不通过页文件,也不会被页文件交换出去。当内存大于3G时,就显得特别有用。因为可以充分利用物理内存。
当物理内存大于4G时,需要/PAE的支持。
以下是一个boot.ini的实例图,是我机器上的:
要使用AWE,需要用户具有Lock Pages in Memory权限,这个在控制面板中的本地计算机政策中设置。
第一,分配进程虚拟空间:
VirtualAlloc (PVOID&开始地址,SIZE_T&大小,DWORD&类型,DWORD&保护属性)
“开始地址”可以是NULL,由系统分配进程空间;“类型”是MEM_RESERVE|MEM_PHYSICAL;“保护属性”只能是
PAGE_READWRITE。
MEM_PHYSICAL指的是区域将受物理存储器的支持。
第二,你要计算出分配的页面数目PageCount:
利用本文第二节的GetSystemInfo可以计算出来。
第三,分配物理内存页面:
AllocateUserPhysicalPages (HANDLE&进程句柄,SIZE_T&页数,ULONG_PTR&页面指针数组)
进程句柄可以用GetCurrentProcess()获得;页数是刚计算出来的页数PageCount;页面数组指针unsigned long* Array[PageCount]。
系统会将分配结果存进这个数组。
第四,将物理内存与虚拟空间进行映射:
MapUserPhysicalPages (PVOID&开始地址,SIZE_T&页数,ULONG_PTR&页面指针数组)
“开始地址”是第一步分配的空间;
这样的话,虚拟地址就可以使用了。
如果“页面指针数组”是NULL,则取消映射。
第五,释放物理页面
FreeUserPhysicalPages (HANDLE&进程句柄,SIZE_T&页数,ULONG_PTR&页面指针数组)
这个除了释放物理页面外,还会取消物理页面的映射。
第六,释放进程空间
VirtualFree (PVOID&开始地址,0,MEM_RELEASE)
C&#43;&#43;程序:
首先,在登录用户有了Lock Pages in Memory权限以后,还需要调用Windows API激活这个权限。
BOOL VirtualMem::LoggedSetLockPagesPrivilege ( HANDLE hProcess,BOOL bEnable)&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&struct&{
&&&&&&&&&&&&&&&&&&&&&&&&DWORD C//数组的个数
&&&&&&&&&&&&&&&&&&&&&&&&LUID_AND_ATTRIBUTES Privilege [1];} I
&&&&&&&&&&&&HANDLE T
&&&&&&&&&&&&//打开本进程的权限句柄
&&&&&&&&&&&&BOOL Result = OpenProcessToken ( hProcess,
&&&&&&&&&&&&&&&&&&&&&&&&TOKEN_ADJUST_PRIVILEGES,
&&&&&&&&&&&&&&&&&&&&&&&&& Token);
&&&&&&&&&&&&If&(Result!= TRUE )
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&printf( &Cannot open process token./n& );
&&&&&&&&&&&&&&&&&&&&&&&&return&FALSE;
&&&&&&&&&&&&}
&&&&&&&&&&&&//我们只改变一个属性
&&&&&&&&&&&&Info.Count = 1;
&&&&&&&&&&&&//准备激活
&&&&&&&&&&&&if( bEnable )
&&&&&&&&&&&&&&&&&&&&Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&&&&&Info.Privilege[0].Attributes = 0;
&&&&&&&&&&&&//根据权限名字找到LGUID
&&&&&&&&&&&&Result = LookupPrivilegeValue ( NULL,
&&&&&&&&&&&&&&&&&&&&&&&&SE_LOCK_MEMORY_NAME,
&&&&&&&&&&&&&&&&&&&&&&&&&(Info.Privilege[0].Luid));
&&&&&&&&&&&&if( Result != TRUE )
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&printf( &Cannot get privilege for %s./n&, SE_LOCK_MEMORY_NAME );
&&&&&&&&&&&&&&&&&&&&&&&&return&FALSE;
&&&&&&&&&&&&}
&&&&&&&&&&&&//&激活Lock Pages in Memory权限
Result = AdjustTokenPrivileges ( Token, FALSE,(PTOKEN_PRIVILEGES) &Info,0, NULL, NULL);
&&&&&&&&&&&&if( Result != TRUE )
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&printf (&Cannot adjust token privileges (%u)/n&, GetLastError() );
&&&&&&&&&&&&&&&&&&&&&&&&return&FALSE;
&&&&&&&&&&&&}
&&&&&&&&&&&&else
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&if( GetLastError() != ERROR_SUCCESS )
&&&&&&&&&&&&&&&&&&&&&&&&{
printf (&Cannot enable the SE_LOCK_MEMORY_NAME &);
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&printf (&please check the local policy./n&);
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&return&FALSE;
&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&&&&&&&&&CloseHandle( Token );
&&&&&&&&&&&&return&TRUE;
分配100M虚拟空间:
PVOID pVirtual=VirtualAlloc(NULL,100*,MEM_RESERVE|MEM_PHYSICAL,PAGE_READWRITE);
&&&&&&&&&&&&if(pVirtual==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&没有那么大连续进程空间!&&&
&&&&&&&&&&&&MEMORYSTATUS memStatusVirtual5;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatusVirtual5);
&&&&&&&&&&&&cout&&&虚拟内存分配:&&&
cout&&&减少物理内存=&&&memStatusVirtual4.dwAvailPhys-memStatusVirtual5.dwAvailPhys&&endl
cout&&&减少可用页文件=&&&memStatusVirtual4.dwAvailPageFile-memStatusVirtual5.dwAvailPageFile&&
&&&cout&&&减少可用进程空间=&
&&memStatusVirtual4.dwAvailVirtual-memStatusVirtual5.dwAvailVirtual&&endl&&
结果如下:
可以看见,只分配了进程空间,没有分配物理内存。
分配物理内存:
ULONG_PTR pages=(ULONG_PTR)100*/sysInfo.dwPageS
&&&&&&&&&&&&ULONG_PTR *frameArray=new&ULONG_PTR[pages];
&&&&&&&&&&&&//如果没激活权限,是不能调用这个方法的,可以调用,但是返回FALSE
BOOL flag=AllocateUserPhysicalPages(GetCurrentProcess(),
&pages,frameArray);
&&&&&&&&&&&&if(flag==FALSE)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&分配物理内存失败!&&&
&&&&&&&&&&&&MEMORYSTATUS memStatusVirtual6;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatusVirtual6);
&&&&&&&&&&&&cout&&&物理内存分配:&&&
cout&&&减少物理内存=&&&memStatusVirtual5.dwAvailPhys-memStatusVirtual6.dwAvailPhys&&endl
cout&&&减少可用页文件=&&&memStatusVirtual5.dwAvailPageFile-memStatusVirtual6.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatusVirtual5.dwAvailVirtual-memStatusVirtual6.dwAvailVirtual&&endl&&
&&结果如下:
分配了物理内存,可能分配时需要进程空间管理。
物理内存映射进程空间:
int* pVInt=(int*)pV
&&&&&&&&&&&&//pVInt[0]=10;这时候访问会出错
&&&&&&&&&&&&flag=MapUserPhysicalPages(pVirtual,1,frameArray);
&&&&&&&&&&&&if(flag==FALSE)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&映射物理内存失败!&&&
&&&&&&&&&&&&MEMORYSTATUS memStatusVirtual7;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatusVirtual7);
&&&&&&&&&&&&cout&&&物理内存分配:&&&
cout&&&减少物理内存=&&&memStatusVirtual6.dwAvailPhys-memStatusVirtual7.dwAvailPhys&&endl
cout&&&减少可用页文件=&&&memStatusVirtual6.dwAvailPageFile-memStatusVirtual7.dwAvailPageFile&&
cout&&&减少可用进程空间=&
&&memStatusVirtual6.dwAvailVirtual-memStatusVirtual7.dwAvailVirtual&&endl&&
结果如下:
这个过程没有损失任何东西。
看看第一次映射和第二次映射的&#20540;:
pVInt[0]=10;
&&&&&&&&&&&&cout&&&第一次映射&#20540;=&&&pVInt[0]&&
&&&&&&&&&&&&&&&&&&&&&&&&flag=MapUserPhysicalPages(pVirtual,1,frameArray&#43;1);
&&&&&&&&&&&&if(flag==FALSE)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&映射物理内存失败!&&&
&&&&&&&&&&&&pVInt[0]=21;
&&&&&&&&&&&&cout&&&第二次映射&#20540;=&&&pVInt[0]&&
&&&&&&&&&&&&flag=MapUserPhysicalPages(pVirtual,1,frameArray);
&&&&&&&&&&&&if(flag==FALSE)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&映射物理内存失败!&&&
&&&&&&&&&&&&cout&&&再现第一次映射&#20540;=&&&pVInt[0]&&
结果如下:
可以看出,第二次映射的&#20540;没有覆盖第一次映射的&#20540;,也就是说,用同一个进程空间地址可以取出两份数据,这样的话,相当于进程的地址空间增大了。
4.&&&&&&内存管理机制--内存映射文件&(Map)
&&& 和虚拟内存一样,内存映射文件可以用来保留一个进程地址区域;但是,与虚拟内存不同,它提交的不是物理内存或是虚拟页文件,而是硬盘上的文件。
·&&&&&&&&使用场合
它有三个主要用途:
系统加载EXE和DLL文件
操作系统就是用它来加载exe和dll文件建立进程,运行exe。这样可以节省页文件和启动时间。
如果文件太大,比如超过了进程用户区2G,用fopen是不能对文件进行操作的。这时,可用内存映射文件。对于大数据文件可以不必对文件执行I/O操作,不必对所有文件内容进行缓存。
进程共享机制
内存映射文件是多个进程共享数据的一种较高性能的有效方式,它也是操作系统进程通信机制的底层实现方法。RPC、COM、OLE、DDE、窗口消息、剪贴板、管道、Socket等都是使用内存映射文件实现的。
·&&&&&&&&系统加载EXE和DLL文件
ü&&&&&&EXE文件&#26684;式
每个EXE和DLL文件由许多节(Section)组成,每个节都有保护属性:READ,WRITE,EXECUTE和SHARED(可以被多个进程共享,关闭页面的COPY-ON-WRITE属性)。
以下是常见的节和作用:
.exe和.dll文件的代码
已经初始化的数据
未初始化的数据
重定位表(装载进程的进程地址空间)
运行期只读数据
C运行期只读数据
异常处理表
线程的本地化存储
输入文件名表
输出文件名表
延迟输入文件名表
ü&&&&&&加载过程
1.&&&&&&系统根据exe文件名建立进程内核对象、页目和页表,也就是建立了进程的虚拟空间。
2.&&&&&&读取exe文件的大小,在默认基地址0x上保留适当大小的区域。可以在链接程序时用/BASE&选项更改基地址(在VC工程属性/链接器/高级上设置)。提交时,操作系统会管理页目和页表,将硬盘上的文件映射到进程空间中,页表中保存的地址是exe文件的页偏移。
3.&&&&&&读取exe文件的.idata节,此节列出exe所用到的所有dll文件。然后和
exe文件一样,将dll文件映射到进程空间中。如果无法映射到基地址,系统会重新定位。
4.&&&映射成功后,系统会把第一页代码加载到内存,然后更新页目和页
表。将第一条指令的地址交给线程指令指针。当系统执行时,发现代码没有在内存中,会将exe文件中的代码加载到内存中。
&&&&&&&&&&&&&&
ü&&&&&&第二次加载时(运行多个进程实例)
1.&&&&&&建立进程、映射进程空间都跟前面一样,只是当系统发现这个exe已
&&&&&&经建立了内存映射文件对象时,它就直接映射到进程空间了;只是当
&&&&&系统分配物理页面时,根据节的保护属性赋予页面保护属性,对于代码
&&&&&节赋予READ属性,全局变量节赋予COPY-ON-WRITE属性。
2.&&&&&&不同的实例共享代码节和其他的节,当实例需要改变页面内容时,会
&&&&&&拷贝页面内容到新页面,更新页目和页表。
3.&&&&&&对于不同进程实例需要共享的变量,exe文件有一
&&&&&&个默认的节,&给这个节赋予SHARED属性。
4.&&&&&&你也可以创建自己的SHARED节
#pragma data_seg(“节名”)
Long instC
#pragma data_seg()
然后,你需要在链接程序时告诉编译器节的默认属性。
/SECTION:&节名,RWS
或者,在程序里用以下表达式:
#pragma comment(linker,“/SECTION:节名,RWS”)
这样的话编译器会创建.drective节来保存上述命令,然后链接时会用它改变节属性。
注意,共享变量有可能有安全隐患,因为它可以读到其他进程的数据。
C&#43;&#43;程序:多个进程共享变量举例
*.cpp开始处:
#pragma&data_seg(&.share&)
long&shareCount=0;
#pragma&data_seg()
#pragma&comment(linker,&/SECTION:.share,RWS&)
ShareCount&#43;&#43;;
注意,同一个exe文件产生的进程会共享shareCount,必须是处于同一个位置上的exe。
·&&&&&&&&访问大数据文件
ü&&&&&&创建文件内核对象
使用CreateFile(文件名,访问属性,共享模式,…)&API可以创建。
其中,访问属性有:
0&不能读写&(用它可以访问文件属性)
GENERIC_READ
GENERIC_WRITE
GENERIC_READ|GENERIC_WRITE;
共享模式:
0&独享文件,其他应用程序无法打开
FILE_SHARE_WRITE
FILE_SHARE_READ|FILE_SHARE_WRITE
这个属性依赖于访问属性,必须和访问属性不冲突。
当创建失败时,返回INVALID_HANDLE_VALUE。
C&#43;&#43;程序如下:
试图打开一个1G的文件:
MEMORYSTATUS memS
GlobalMemoryStatus(&memStatus);
HANDLE hn=CreateFile(L&D://1G.rmvb&,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
&&&&&&&&&&&&&&if(hn==INVALID_HANDLE_VALUE)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&打开文件失败!&&&
&&&&&&&&&&&&&&FILE *p=fopen(&D://1G.rmvb&,&rb&);
&&&&&&&&&&&&&&if(p==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&用fopen不能打开大文件!&&&
&&&&&&&&&&&&&&MEMORYSTATUS memStatus2;
&&&&&&&&&&&&&&GlobalMemoryStatus(&memStatus2);
&&&&&&&&&&&&&&cout&&&打开文件后的空间:&&&
cout&&&减少物理内存=&&&memStatus.dwAvailPhys-memStatus2.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus.dwAvailPageFile-memStatus2.dwAvailPageFile&&
cout&&&减少可用进程空间=&
&&memStatus.dwAvailVirtual-memStatus2.dwAvailVirtual&&endl&&
结果如下:
可见,系统需要一些内存来管理内核对象,每一次运行的结果都不一样,但差别不会太大。
用的fopen不能打开这么大的文件。理论上,32位系统能支持232字节,但是,进程空间只有2G,它只能表示那么大的空间。
ü&&&&&&创建文件映射内核对象
HANDLE CreateFileMapping(Handle&文件,PSECURITY_ATTRIBUTES&安全属性,DWORD&保护属性,DWORD&文件大小高32位,DWORD&文件大小低32位,PCTSTR&&映射名称)
“文件”是上面创建的句柄;
“安全属性”是内核对象需要的,NULL表示使用系统默认的安全属性;“保护属性”是当将存储器提交给进程空间时,需要的页面属性:PAGE_READONLY, PAGE_READWRITE和PAGE_WRITECOPY。这个属性不能和文件对象的访问属性冲突。除了这三个外,还有两个属性可以和它们连接使用(|)。当更新文件内容时,不提供缓存,直接写入文件,可用SEC_NOCACHE;当文件是可执行文件时,系统会根据节赋予不同的页面属性,可用SEC_IMAGE。另外,SEC_RESERVE和SEC_COMMIT用于稀疏提交的文件映射,详细介绍请参考下文。
“文件大小高32位”和“文件大小低32位”联合起来告诉系统,这个映射所能支持的文件大小(操作系统支持264B文件大小);当这个&#20540;大于实际的文件大小时,系统会扩大文件到这个&#20540;,因为系统需要保证进程空间能完全被映射。&#20540;为0默认为文件的大小,这时候如果文件大小为0,创建失败。
“映射名称”是给用户标识此内核对象,供各进程共享,如果为NULL,则不能共享。
对象创建失败时返回NULL。
创建成功后,系统仍未为文件保留进程空间。
C&#43;&#43;程序:
&&&&&&&&&&&&&&&&&&&&&&&&MEMORYSTATUS memStatus2;
&&&&&&&&&&&&&&&&&&&&&&&&GlobalMemoryStatus(&memStatus2);
HANDLE hmap=CreateFileMapping(hn,NULL,PAGE_READWRITE,0,0,L&Yeming-Map&);
&&&&&&&&&&&&&&&&&&&&&&&&if(hmap==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&建立内存映射对象失败!&&&
&&&&&&&&&&&&&&&&&&&&&&&&MEMORYSTATUS memStatus3;
&&&&&&&&&&&&&&&&&&&&&&&&GlobalMemoryStatus(&memStatus3);
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&建立内存映射文件后的空间:&&&
cout&&&减少物理内存=&&&memStatus2.dwAvailPhys-memStatus3.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus2.dwAvailPageFile-memStatus3.dwAvailPageFile&&
&&&&&&&&&cout&&&减少可用进程空间=&
&&memStatus2.dwAvailVirtual-memStatus3.dwAvailVirtual&&endl&&
&&&&&&&&&&&&结果如下:
默认内存映射的大小是1G文件。没有损失内存和进程空间。它所做的是建立内核对象,收集一些属性。
ü&&&&&&文件映射内核对象映射到进程空间
PVOID MAPViewOfFile(HANDLE&映射对象,DWORD访问属性,DWORD&偏移量高32位,DWORD&偏移量低32位,SIZE_T&字节数)
“映射对象”是前面建立的对象;
“访问属性”可以是下面的&#20540;:FILE_MAP_WRITE(读和写)、FILE_MAP_READ、FILE_MAP_ALL_ACCESS(读和写)、FILE_MAP_COPY。当使用FILE_MAP_COPY时,系统分配虚拟页文件,当有写操作时,系统会拷贝数据到这些页面,并赋予PAGE_READWRITE属性。
可以看到,每一步都需要设置这类属性,是为了可以多点控制,试想,如果在这一步想有多种不同的属性操作文件的不同部分,就比较有用。
“偏移高32位”和“偏移低32位”联合起来标识映射的开始字节(地址是分配粒度的倍数);
“字节数”指映射的字节数,默认0为到文件尾。
当你需要指定映射到哪里时,你可以使用:
PVOID MAPViewOfFile(HANDLE&映射对象,DWORD访问属性,DWORD&偏移量高32位,DWORD&偏移量低32位,SIZE_T&字节数,PVOID&基地址)
“基地址”是映射到进程空间的首地址,必须是分配粒度的倍数。
C&#43;&#43;程序:
MEMORYSTATUS memStatus3;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus3);
&&&&&&&&&&&&LPVOID pMAP=MapViewOfFile(hmap,FILE_MAP_WRITE,0,0,0);
&&&&&&&&&&&&cout&&&映射内存映射文件后的空间:&&&
if(pMAP==NULL)
&&&&&&&&&&&&&&&cout&&&映射进程空间失败!&&&
&&&&&&&&&&&&else
&&&&&&&&&&&&&&&printf(&首地址=%x/n&,pMAP);
&&&&&&&&&&&&MEMORYSTATUS memStatus4;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus4);
cout&&&减少物理内存=&&&memStatus3.dwAvailPhys-memStatus4.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus3.dwAvailPageFile-memStatus4.dwAvailPageFile&&
cout&&&减少可用进程空间=&
&&memStatus3.dwAvailVirtual-memStatus4.dwAvailVirtual&&endl&&
结果如下:
进程空间减少了1G,系统同时会开辟一些内存来做文件缓存。
ü&&&&&&使用文件
1.&&&&&&对于大文件,可以用多次映射的方法达到访问的目的。有点像AWE技术。
2.&&&&&&Windows只保证同一文件映射内核对象的多次映射的数据一致性,比如,当有两次映射同一对象到二个进程空间时,一个进程空间的数据改变后,另一个进程空间的数据也会跟着改变;不保证不同映射内核对象的多次映射的一致性。所以,使用文件映射时,最好在CreateFile时将共享模型设置为0独享,当然,对于只读文件没这个必要。
&&&&C&#43;&#43;程序:使用1G的文件
MEMORYSTATUS memStatus4;
&&&&&&&&&&&&&&&&&&&&&&&&GlobalMemoryStatus(&memStatus4);
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&读取1G文件前:&&&
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&可用物理内存=&&&memStatus4.dwAvailPhys&&
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&可用页文件=&&&memStatus4.dwAvailPageFile&&
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&可用进程空间=&&&memStatus4.dwAvailVirtual&&endl&&
&&&&&&&&&&&&&&&&&&&&&&&&int* pInt=(int*)pMAP;
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&更改前=&&&pInt[/4-1]&&//文件的最后一个整数
&&&&&&&&&&&&&&&&&&&&&&&&for(int&i=0;i&/4-1;i&#43;&#43;)
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&pInt[i]&#43;&#43;;
&&&&&&&&&&&&&&&&&&&&&&&&pInt[/4-1]=10;
&&&&&&&&&&&&&&&&&&&&&&&&pInt[100]=90;
&&&&&&&&&&&&&&&&&&&&&&&&pInt[101]=100;
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&读取1G文件后:&&&
&&&&&&&&&&&&&&&&&&&&&&&&MEMORYSTATUS memStatus5;
&&&&&&&&&&&&&&&&&&&&&&&&GlobalMemoryStatus(&memStatus5);
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&可用物理内存=&&&memStatus5.dwAvailPhys&&
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&可用页文件=&&&memStatus5.dwAvailPageFile&&
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&可用进程空间=&&&memStatus5.dwAvailVirtual&&endl&&
&&&&&&&&&&&
结果如下:
程序将1G文件的各个整型数据加1,从上图看出内存损失了600多兆,但有时候损失不过十几兆,可能跟系统当时的状态有关。
不管怎样,这样你完全看不到I/O操作,就像访问普通数据结构一样方便。
ü&&&&&&保存文件修改
为了提高速度,更改文件时可能只更改到了系统缓存,这时,需要强制保存更改到硬盘,特别是撤销映射前。
BOOL FlushViewOfFile(PVOID&进程空间地址,SIZE_T&字节数)
“进程空间地址”指的是需要更改的第一个字节地址,系统会变成页面的地址;
“字节数”,系统会变成页面大小的倍数。
写入磁盘后,函数返回,对于网络硬盘,如果希望写入网络硬盘后才返回的话,需要将FILE_FLAG_WRITE_THROUGH参数传给CreateFile。
当使用FILE_MAP_COPY建立映射时,由于对数据的更改只是对虚拟页文件的修改而不是硬盘文件的修改,当撤销映射时,会丢失所做的修改。如果要保存,怎么办?
你可以用FILE_MAP_WRITE建立另外一个映射,它映射到进程的另外一段空间;扫描第一个映射的PAGE_READWRITE页面(因为属性被更改),如果页面改变,用MoveMemory或其他拷贝函数将页面内容拷贝到第二次映射的空间里,然后再调用FlushViewOfFile。当然,你要记录哪个页面被更改。
ü&&&&&&撤销映射
用以下API可以撤销映射:
BOOL&&UnmapViewOfFile(PVOID pvBaseAddress)
这个地址必须与MapViewOfFile返回&#20540;相同。
ü&&&&&&关闭内核对象
在不需要内核对象时,尽早将其释放,防止内存泄露。由于它们是内核对象,调用CloseHandle(HANDLE)就可以了。
在CreateFileMapping后马上关闭文件句柄;
在MapViewOfFile后马上关闭内存映射句柄;
最后再撤销映射。
·&&&&&&&&进程共享机制
ü&&&&&&基于硬盘文件的内存映射
如果进程需要共享文件,只要按照前面的方式建立内存映射对象,然后按照名字来共享,那么进程就可以映射这个对象到自己的进程空间中。
C&#43;&#43;程序如下:
HANDLE mapYeming=OpenFileMapping(FILE_MAP_WRITE,true,L&Yeming-Map&);
&&&&&&&&&&&&&&&&&&&&&&&&if(mapYeming==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&找不到内存映射对象:Yeming-Map!&&&
&&&&&&&&&&&&&&&&&&&&&&&&MEMORYSTATUS memStatus3;
&&&&&&&&&&&&&&&&&&&&&&&&GlobalMemoryStatus(&memStatus3);
LPVOID pMAP=MapViewOfFile(mapYeming,FILE_MAP_WRITE,0,0,);
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&建立内存映射文件后的空间:&&&
&&&&&&&&&&&&&&&&&&&&&&&&if(pMAP==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&映射进程空间失败!&&&
&&&&&&&&&&&&&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&&&&&printf(&首地址=%x/n&,pMAP);
&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&MEMORYSTATUS memStatus4;
&&&&&&&&&&&&&&&&&&&&&&&&GlobalMemoryStatus(&memStatus4);
&&&&&&&&&&&
cout&&&减少物理内存=&&&memStatus3.dwAvailPhys-memStatus4.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus3.dwAvailPageFile-memStatus4.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatus3.dwAvailVirtual-memStatus4.dwAvailVirtual&&endl&&
&&&&&&&&&&&&&&&&&&&&&&&&int* pInt=(int*)pMAP;
&&&&&&&&&cout&&pInt[100]&&
&&&&&&&&&结果如下:
在2.exe中打开之前1.exe创建的内存映射对象(当然,1.exe得处于运行状态),然后映射进自己的进程空间,当1.exe改变文件的&#20540;时,2.exe的文件对应&#20540;也跟着改变,Windows保证同一个内存映射对象映射出来的数据是一致的。可以看见,1.exe将&#20540;从90改为91,2.exe也跟着改变,因为它们有共同的缓冲页。
ü&&&&&&基于页文件的内存映射
如果只想共享内存数据时,没有必要创建硬盘文件,再建立映射。可以直
接建立映射对象:
只要传给CreateFileMapping一个文件句柄INVALID_HANDLE_VALUE就行了。所以,CreateFile时,一定要检查返回&#20540;,否则会建立一个基于页文件的内存映射对象。接下来就是映射到进程空间了,这时,系统会分配页文件给它。
C&#43;&#43;程序如下:
HANDLE hPageMap=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,
,L&Yeming-Map-Page&);
&&&&&&&&&&&&if(hPageMap==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&建立基于页文件的内存映射对象失败!&&&
&&&&&&&&&&&&MEMORYSTATUS memStatus6;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus6);
&&&&&&&&&&&&cout&&&建立基于页文件的内存映射文件后的空间:&&&
cout&&&减少物理内存=&&&memStatus5.dwAvailPhys-memStatus6.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus5.dwAvailPageFile-memStatus6.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatus5.dwAvailVirtual-memStatus6.dwAvailVirtual&&endl&&&&&&&&&&&&&
LPVOID pPageMAP=MapViewOfFile(hPageMap,FILE_MAP_WRITE,0,0,0);&&&&&&&&
&&&&&&&&&&&&结果如下:
可见,和基于数据文件的内存映射不同,现在刚建立内核对象时就分配了所要的100M内存。好处是,别的进程可以通过这个内核对象共享这段内存,只要它也做了映射。
ü&&&&&&稀疏内存映射文件
在虚拟内存一节中,提到了电子表&#26684;程序。虚拟内存解决了表示很少单元&#26684;有数据但必须分配所有内存的内存浪费问题;但是,如果想在多个进程之间共享这个电子表&#26684;结构呢?
如果用基于页文件的内存映射,需要先分配页文件,还是浪费了空间,没有了虚拟内存的优点。
Windows提供了稀疏提交的内存映射机制。
当使用CreateFileMapping时,保护属性用SEC_RESERVE时,其不提交物理存储器,使用SEC_COMMIT时,其马上提交物理存储器。注意,只有文件句柄为INVALID_HANDLE_VALUE时,才能使用这两个参数。
按照通常的方法映射时,系统只保留进程地址空间,不会提交物理存储器。
当需要提交物理内存时才提交,利用通常的VirtualAlloc函数就可以提交。
当释放内存时,不能调用VirtualFree函数,只能调用UnmapViewOfFile来撤销映射,从而释放内存。
C&#43;&#43;程序如下:
HANDLE hVirtualMap=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE|SEC_RESERVE,0,,L&Yeming-Map-Virtual&);
if(hPageMap==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&建立基于页文件的稀疏内存映射对象失败!&&&
&&&&&&&&&&&&MEMORYSTATUS memStatus8;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus8);
&&&&&&&&&&&&cout&&&建立基于页文件的稀疏内存映射文件后的空间:&&&
cout&&&减少物理内存=&&&memStatus7.dwAvailPhys-memStatus8.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus7.dwAvailPageFile-memStatus8.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatus7.dwAvailVirtual-memStatus8.dwAvailVirtual&&endl&&
&&&&&&&&&&&
LPVOID pVirtualMAP=MapViewOfFile(hVirtualMap,FILE_MAP_WRITE,0,0,0);
&&&&&&&&&&&&cout&&&内存映射进程后的空间:&&&
&&&&&&&&&&&&if(pVirtualMAP==NULL)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&映射进程空间失败!&&&
&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&&&&&&&&&printf(&首地址=%x/n&,pVirtualMAP);
&&&&&&&&&&&
&&&&&&&&&&&&MEMORYSTATUS memStatus9;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus9);
&&&&&&&&&&&
cout&&&减少物理内存=&&&memStatus8.dwAvailPhys-memStatus9.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus8.dwAvailPageFile-memStatus9.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatus8.dwAvailVirtual-memStatus9.dwAvailVirtual&&endl&&
结果如下:
用了SEC_RESERVE后,只是建立了一个内存映射对象,和普通的一样;不同的是,它映射完后,得到了一个虚拟进程空间。现在,这个空间没有分配任何的物理存储器给它,你可以用VirtualAlloc&提交存储器给它,详细请参考上一篇&虚拟内存(VM)&。
注意,你不可以用VirtualFree来释放了,只能用UnmapViewOfFile来。
C&#43;&#43;程序如下:
LPVOID pP=VirtualAlloc(pVirtualMAP,100*,MEM_COMMIT,PAGE_READWRITE);&
&&&&&&&&&&&&MEMORYSTATUS memStatus10;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus10);
&&&&&&&&&&&
cout&&&减少物理内存=&&&memStatus9.dwAvailPhys-memStatus10.dwAvailPhys&&
cout&&&减少可用页文件=&&&memStatus9.dwAvailPageFile-memStatus10.dwAvailPageFile&&
cout&&&减少可用进程空间=&&&memStatus9.dwAvailVirtual-memStatus10.dwAvailVirtual&&endl&&
&&&&&&&&&&&&bool&result=VirtualFree(pP,,MEM_DECOMMIT);
&&&&&&&&&&&&if(!result)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&释放失败!&&&
&&&&&&&&&&&&&result=VirtualFree(pP,,MEM_RELEASE);
&&&&&&&&&&&&if(!result)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&释放失败!&&&
&&&&&&&&&&&&CloseHandle(hVirtualMap);
&&&&&&&&&&&&MEMORYSTATUS memStatus11;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus11);
cout&&&增加物理内存=&&&memStatus11.dwAvailPhys-memStatus10.dwAvailPhys&&
cout&&&增加可用页文件=&&&memStatus11.dwAvailPageFile-memStatus10.dwAvailPageFile&&
cout&&&增加可用进程空间=&&&memStatus11.dwAvailVirtual-memStatus10.dwAvailVirtual&&endl&&
&&&&&&&&&&&&result=UnmapViewOfFile(pVirtualMAP);
&&&&&&&&&&&&if(!result)
&&&&&&&&&&&&&&&&&&&&&&&&cout&&&撤销映射失败!&&&
&&&&&&&&&&&&MEMORYSTATUS memStatus12;
&&&&&&&&&&&&GlobalMemoryStatus(&memStatus12);
cout&&&增加物理内存=&&&memStatus12.dwAvailPhys-memStatus11.dwAvailPhys&&
cout&&&增加可用页文件=&&&memStatus12.dwAvailPageFile-memStatus11.dwAvailPageFile&&
cout&&&增加可用进程空间=&
&&memStatus12.dwAvailVirtual-memStatus11.dwAvailVirtual&&endl&&
结果如下:
可以看见,用VirtualFree是不能够释放这个稀疏映射的;最后用UnmapViewOfFile得以释放进程空间和物理内存。
6.&&&&&&内存管理机制--堆栈&(Stack)
·&&&&&&&&使用场合
操作系统为每个线程都建立一个默认堆栈,大小为1M。这个堆栈是供函数调用时使用,线程内函数里的各种静态变量都是从这个默认堆栈里分配的。
·&&&&&&&&堆栈结构
默认1M的线程堆栈空间的结构举例如下,其中,基地址为0x,刚开始时,CPU的堆栈指针寄存器保存的是栈顶的第一个页面地址0x。第二页面为保护页面。这两页是已经分配物理存储器的可用页面。
随着函数的调用,系统将需要更多的页面,假设需要另外5页,则给这5页提交内存,删除原来页面的保护页面属性,最后一页赋予保护页面属性。
当分配倒数第二页0x时,系统不再将保护属性赋予它,相反,它会产生堆栈溢出异常STATUS_STACK_OVERFLOW,如果程序没有处理它,则线程将退出。最后一页始终处于保留状态,也就是说可用堆栈数是没有1M的,之所以不用,是防止线程破坏栈底下面的内存(通过违规访问异常达到目的)。
当程序的函数里分配了临时变量时,编译器把堆栈指针递减相应的页数目,堆栈指针始终都是一个页面的整数倍。所以,当编译器发现堆栈指针位于保护页面之下时,会插入堆栈检查函数,改变堆栈指针及保护页面。这样,当程序运行时,就会分配物理内存,而不会出现访问违规。
·&&&&&&&&使用例子
改变堆栈默认大小:
有两个方法,一是在CreateThread()时传一个参数进去改变;
二是通过链接命令:
#pragma comment(linker,&/STACK:24000&)
第一个&#20540;是堆栈的保留空间,第二个&#20540;是堆栈开始时提交的物理内存大小。本文将堆栈改变为100M。
&&&&&&&&&堆栈溢出处理:
&&&&&&&&如果出现堆栈异常不处理,则导致线程终止;如果你只做了一般处理,内&存
&&&&&&&&结构已经处于破坏状态,因为已经没有保护页面,系统没有办法再抛出堆栈溢
&&&&&&&&出异常,这样的话,当再次出现溢出时,会出现访问违规操作
&&&&&&&&STATUS_ACCESS_VIOLATION,这是线程将被系统终止。解决办法是,恢复
&&&&&&&堆栈的保护页面。请看以下例子:
&&&&&&&C&#43;&#43;程序如下:
bool&handle=true;
&&&&&&&&&&&&static&MEMORY_BASIC_INFORMATION
&&&&&&&&&&&&LPBYTE lpP
&&&&&&&&&&&&//得到堆栈指针寄存器里的&#20540;
&&&&&&&&&&&&_asm&mov lpPage,
&&&&&&&&&&&&//&得到当前堆栈的一些信息
&&&&&&&&&&&&VirtualQuery(lpPage, &mi,&sizeof(mi));
&&&&&&&&&&&&//输出堆栈指针
&&&&&&&&&&&&printf(&堆栈指针=%x/n&,lpPage);
&&&&&&&&&&&&//&这里是堆栈的提交大小
&&&&&&&&&&&&printf(&已用堆栈大小=%d/n&,mi.RegionSize);
&&&&&&&&&&&&printf(&堆栈基址=%x/n&,mi.AllocationBase);
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&for(int&i=0;i&2;i&#43;&#43;)
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&__try
&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&__try
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&__try
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&cout&&&**************************&&&
&&&&&&&&&&&&&&&&&&&&&&&&//如果是这样静态分配导致的堆栈异常,系统默认不抛出异常,捕获不到
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//char a[];
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//动态分配栈空间,有系统调用Alloca实现,自动释放
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Add(1000);
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//系统可以捕获违规访问
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&int&* p=(int*)0xC;
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*p=3;
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&cout&&&执行结束&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&__except(GetExceptionCode()==STATUS_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&cout&&&Excpetion 1&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&__except(GetExceptionCode()==STATUS_STACK_OVERFLOW ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&cout&&&Exception 2&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&if(handle)
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//做堆栈破坏状态恢复
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&LPBYTE lpP
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&static&SYSTEM_INFO
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&static&MEMORY_BASIC_INFORMATION
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&static&DWORD dwOldP
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//&得到内存属性
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&GetSystemInfo(&si);
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//&得到堆栈指针
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&_asm&mov lpPage,

我要回帖

更多关于 二维数组动态分配内存 的文章

 

随机推荐