版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
10.1.1内存管理基础Win32中的内存管理是分层次的,系统提供了几组层次不同的函数来管理内存,它们是标准内存管理函数、堆管理函数、虚拟内存管理函数和内存映射文件函数。所有的这些函数都是为了让用户能在比较高的层次上方便地管理内存,以便将程序和底层的内存分页机制隔离开来。图说内存:10.1.1内存管理基础Windows的内存分层管理1.虚拟内存管理函数Windows使用一个以页为基础的虚拟内存系统,与分页有关的概念已经在该系列教程刚开始几讲中有所介绍,虽然那时候大家还懵懵懂懂。Windows充分利用了80x86处理器保护模式下的线性寻址机制和分页机制,这些机制是Win32内存管理的基础。Win32提供了一组虚拟内存管理函数来管理虚拟内存,主要用于保留/提交/释放虚拟内存,在虚拟内存页上改变保护方式,锁定虚拟内存页以及查询一个进程的虚拟内存等操作,这是一组位于底层的函数。2.堆管理函数堆管理函数相对比较高级一点,堆的主要功能就是有效地管理内存和进程的地址空间。DOS操作系统下的C语言中就已经有了”堆”的概念,这时的”堆”是程序初始化时向操作系统申请并预留的大块内存,程序通过C函数在这块空间中申请和释放内存。在Win32中,进程可以使用的整个地址空间就是一个堆。3.标准内存管理函数并且”堆”的概念又被引伸了一步:Win32中分两种堆一种是进程的”默认堆”,默认堆只有一个,指的就是可以使用的整个地址空间;另一种是”动态堆”,也称为”私有堆”,私有堆类似于DOS下C语言中使用的那种堆,一个进程可以随意建立多个私有堆,也可以随意将它们释放。私有堆全部位于默认堆中,从概念上看,它和默认堆并没有什么不同,使用堆管理函数可以对所有的私有堆和默认堆进行操作。标准内存管理函数总是在默认堆中分配和释放内存,这组函数就是常规意义上的内存管理函数。4.内存映射文件函数内存映射文件函数相对比较独立,它是为了文件操作的方便性而设立的。当对文件进行操作的时候,一般先打开文件,申请一块内存用做缓冲区,再将文件数据循环读入并处理,当文件长度大于缓冲区长度则需要多次读入,每次读入后处理缓冲区边界位置的数据往往是个麻烦问题。还记得我们曾经介绍过Windows可以使用磁盘文件当做虚拟内存,内存映射文件函数使用同样的办法将一个文件直接映射到进程的地址空间中,这样可以通过内存指针用读写内存的办法直接存取文件内容。不同内存管理函数的操作对象对比这些函数,可以发现它们涉及的系统资源是各不相同的:内存管理函数涉及方面标准内存管理函数一个进程的默认堆堆管理函数一个进程的虚拟地址空间、系统内存、进程堆资源结构虚拟内存管理函数一个进程的虚拟地址空间、系统页文件、系统内存、硬盘空间内存映射文件函数一个进程的虚拟地址空间、系统页文件、系统内存、硬盘空间、标准文件I/O10.1.2获取内存的当前状态为什么需要此操作?!因为我们之前介绍过,一个进程可以寻址的地址空间是4GB,但用户可以直接管理的地址空间是多大呢?实际上,高端的2GB是供操作系统内核使用的,其中安排了操作系统的代码和数据,可供应用程序使用的地址空间是低端的2GB,这2GB除去应用程序与用户DLL等的代码和静态数据段以后,余下来的才是内存管理函数可以使用的地址空间,应用程序和用户DLL的大小一般只有几兆字节到上百兆字节,所以可以认为能自由使用的地址空间基本上是2GB。10.1.2获取内存的当前状态既然用户可以使用的地址空间大概为2GB,但千万不要天真的认为就可以申请2GB的内存了。因为这2GB仅是可以使用的”地址”空间,而不是可以使用的”内存”空间(请务必谨慎区分),可分配内存的大小还受制于物理内存和磁盘交换文件的大小。因为物理内存和磁盘交换文件是供整个系统和所有用户程序使用的,所有系统内核、当前执行的所有用户程序的代码、数据以及分配的内存总量并不能超过物理内存和磁盘交换文件的总和。10.1.2获取内存的当前状态当设计一个可能需要申请大量内存的程序时,如何预先得知系统的配置情况呢?对此可以使用GlobalMemoryStatus
函数:invokeGlobalMemoryStatus,lpBufferlpBuffer
指向一个MEMORYSTATUS结构,结构的定义如下:10.1.2获取内存的当前状态MEMORYSTATUSSTRUCT
dwLengthDWORD?;本结构的长度
dwMemoryLoadDWORD?;已用内存的百分比
dwTotalPhysDWORD?;物理内存总量
dwAvailPhysDWORD?;可用物理内存
dwTotalPageFileDWORD?;交换文件总的大小
dwAvailPageFileDWORD?;交换文件中空闲部分大小
dwTotalVirtualDWORD?;用户可用的地址空间
dwAvailVirtualDWORD?;当前空闲的地址空间MEMORYSTATUSENDS10.1.2获取内存的当前状态在调用之前需要首先将dwLength
字段设置为MEMORYSTATUS
结构的长度。当调用GlobalMemoryStatus
函数后,函数会在结构中填充对应的数值。注意:dwTotalPageFile
字段返回的是交换文件的最大值,并不是当前实际建立的交换文件的大小,一般当前的交换文件大小会小于这个数值,但这个数值的大小也不是确定的,如果需要的话,系统会增加它的大小直到不再有空余的磁盘空间放置交换文件为止。10.1.2获取内存的当前状态我们来分析一个不完美的例子:MemInfo10.1.2获取内存的当前状态关于GlobalMemoryStatus,MSDN上有这样一段话:Oncomputerswithmorethan4GBofmemory,theMEMORYSTATUSstructurecanreturnincorrectinformation.Windows
2000reportsavalueof-1toindicateanoverflow.EarlierversionsofWindowsNTreportavaluethatistherealamountofmemory,modulo4GB.Ifyourapplicationisatriskforthisbehavior,usetheGlobalMemoryStatusExfunctioninsteadoftheGlobalMemoryStatusfunction.Haveatry先把视频暂停片刻。。。自己试着改一下。。。。小甲鱼接着会讲解支持4GB以上的解决方案,但希望大家先自己根据提示进行探索!支持4GB以上资源获取解析:MemInfo210.1.3标准内存管理函数标准内存管理函数的功能是在进程的默认堆中申请和释放内存块,它由下面一些函数组成:GlobalAlloc,GlobalFree
和GlobalReAlloc
分别用来申请、释放和修改内存大小。GlobalLock
和GlobalUnlock
用来进行锁定操作。GlobalDiscard,GlobalFlags,GlobalHandle
和GlobalSize
等用来丢弃内存或获取已分配内存的一些信息。10.1.3标准内存管理函数友情提示:在Win16中,内存管理函数有"全局"或"本地"之分,它们的区别在于返回的指针是远指针还是近指针,全局内存管理函数名是以"Global"开头的,而"本地"内存管理函数名是以"Local"开头的。在Win32中,指针并没有远近之分,只有一种32位的指针,但为了保持向下兼容,这些函数名仍然沿用了下来。这两组函数在Win32中是完全相同的,读者可以自由使用名字以Global或Local为前缀的函数。10.1.3标准内存管理函数用标准内存管理函数可以分配的内存有两种:固定地址的内存块和可移动的内存块,而可移动的内存块又可以进一步定义为可丢弃的,让我们逐步来讨论它们的不同。固定的内存块常规意义上的内存就是固定的内存块,因为申请到内存后,这块内存的线性地址是固定不变的。要申请一块固定的内存,可以使用函数:invokeGlobalAlloc,GMEM_FIXEDor\GMEM_ZEROINIT,dwBytes1.固定的内存块解释一下:第一个参数是标志,GMEM_FIXED表示申请的是固定的内存块,GMEM_ZEROINIT表示需要将内存块中的所有字节预先初始化为0,也可以简单地使用GPTR标志,它就相当于是GMEM_FIXEDorGMEM_ZEROINIT。第2个参数dwBytes
指出了需要申请的是以字节为单位的内存大小。如果内存申请失败,eax
中返回NULL,否则返回值是一个指向内存块起始地址的指针,用户需要保存这个指针,在使用内存或者释放内存的时候还要用到它。1.固定的内存块如果要释放一个先前申请的固定内存块,可以使用GlobalFree函数:invokeGlobalFree,lpMemory如果释放成功,函数返回NULL,否则函数返回的值就是输入的lpMemory。另外程序在不再使用内存块的时候应该使用这个函数将内存释放,不过就算程序在退出的时候忘记了释放内存,Windows也会自动将它们释放。1.固定的内存块在实际使用中往往需要改变一个内存块的大小,这时候就要用到GlobalReAlloc函数。该函数可以缩小或扩大一块已经申请到的内存:invokeGlobalReAlloc,lpMemory,dwBytes,uFlags.ifeaxmovlpNewMemory,eax.endif1.固定的内存块lpMemory是先前申请的内存块指针,dwBytes是新的大小,如果这个数值比原来申请的时候要小,也就是需要缩小内存块,那么uFlags标志参数可以是NULL。如果缩小内存块的操作不成功,那么函数的返回值为0,否则是新的缩小了的内存块指针,当然,这个指针和原来的指针肯定是一样的。但是需要扩大一个内存块的时候,情况就稍微有些复杂了。1.固定的内存块我们来考虑这样一种情况:首先申请两个1000h大小的固定内存块,得到两个指针,可以发现第二块几乎紧接第一块内存的。(请自行尝试)一般情况下如果第一块内存的地址是X,那么第二块内存的地址几乎就是X+1000h,如果需要将第一个内存块扩大到2000h字节,那么只能在别的地方开辟一个2000h大小的内存块,因为原来位置后面的1000h已经被第二块内存占用了,这就意味着新的指针可能和原来的不一样。1.固定的内存块可以在GlobalReAlloc函数中通过指定不同的uFlags来规定是否允许Windows在必要的时候移动内存块。当uFlags中有GMEM_MOVEABLE选项的时候,如果需要移动内存块,Windows会在别的地方开辟一块新的内存,并把原来内存块中的内容自动复制到新的内存块中,这时函数的返回值是新的指针,原来的指针作废。1.固定的内存块如果不指定GMEM_MOVEABLE选项,那么只有当内存块后面扩展所需的空间没有被使用时,函数才会执行成功,否则,函数失败并返回NULL,这时原来的指针继续有效。为了保证内存块扩大成功,建议总是使用下面的语句来扩大和缩小内存:invokeGlobalReAlloc,lpMemory,dwBytes,\
GMEM_ZEROINITorGMEM_MOVEABLE.ifeax movlpMemory,eax.endif1.固定的内存块指定GMEM_ZEROINIT选项可以使内存块扩大的部分自动被初始化为0,然后程序判断返回值。如果改变大小成功的话,则用新的指针替换原来的指针,其他和原来指针有关的值也不要忘了同时更新。接下来,我们来讨论:可移动的内存块2.可移动的内存块可移动的内存块在不使用的时候允许Windows改变它的线性地址,为什么要使用可移动的内存块呢?惟一的理由是防止内存的碎片化,当进程长时间频繁地申请和释放不同大小的内存块后,申请的大量小块内存可能零散地分布在整个地址空间。尽管空闲的内存总数不小,但久而久之却没有剩下连续的大块空闲地址,最终导致无法再申请大块的内存。2.可移动的内存块解决内存碎片化的办法很简单,因为碎片之间有大量的内存是空闲的,只要允许Windows移动小块的在用内存,就可以将碎片合并成大块的空闲内存。但是在用内存被移动后,程序中对应的指针也要随着改变,不然就会造成访问地址错误。另外,在使用内存的过程中,内存需要有个锁定的过程,不然用到一半就被Windows移动了,结果依然是错误的。2.可移动的内存块只有程序将内存解锁,Windows才可以自由移动它们,这就引申出可移动内存块的概念和操作的基本方法。要申请一个可移动的内存块,使用的函数还是GlobalAlloc,但需要使用不同的参数:invokeGlobalAlloc,GMEM_MOVEABLEor\GMEM_ZEROINIT,dwBytes.ifeax movhMemory,eax.endif2.可移动的内存块GMEM_MOVEABLE
标志指定了分配的内存是可移动的,GMEM_ZEROINIT
同样表示将申请到的内存块的内容初始化为0(也可以用GHND
标志,它就相当于GMEM_MOVEABLEorGMEM_ZEROINIT)。如果内存申请失败,eax中返回NULL,成功的话返回值是一个句柄而不是内存指针,用户需要保存这个句柄,在锁定或释放内存的时候还要用到它。一个进程可以申请的可移动内存的块数最大不能超过65536个,申请固定内存块时则没有数量限制。2.可移动的内存块要使用可移动内存之前,需要把它锁定,这相当于告诉Windows现在程序要使用这块内存了,不能将它移动,锁定内存使用GlobalLock函数:invokeGlobalLock,hMemory.ifeax movlpMemory,eax.endif函数的入口参数是GlobalAlloc返回的内存句柄,如果锁定成功,函数返回一个指针,如果锁定失败,则函数返回NULL。2.可移动的内存块当程序暂时不需要操作这块内存的时候,应该将它解锁,否则和使用固定的内存块就没有区别了,解锁使用GlobalUnlock函数:invokeGlobalUnlock,hMemory函数的参数同样是GlobalAlloc返回的句柄,解锁成功的话函数返回非0值。有个问题:在多线程的程序中,两个地方同时锁定内存,但当一个地方还在使用的情况下另一个地方却调用GlobalUnlock将内存解锁了怎么办?2.可移动的内存块其实不用担心这个问题,Windows为每个可移动的内存句柄维护一个锁定计数,每次锁定内存的时候计数加1,解锁的时候计数减1,只有当计数为0的时候内存才真正被解锁。所以只要程序中的GlobalLock函数和GlobalUnlock函数是配对的,就不用担心这个问题。2.可移动的内存块要释放一个可移动的内存块,同样使用GlobalFree函数:invokeGlobalFree,hMemory但使用的参数是GlobalAlloc返回的内存句柄,如果释放成功,函数返回NULL。不管内存当前是否处在锁定状态,都可以被成功释放。2.可移动的内存块调整可移动内存块的大小,同样使用GlobalReAlloc函数:invokeGlobalReAlloc,hMemory,dwBytes,\GMEM_ZEROINITorGMEM_MOVEABLE如果调整成功,返回值就是输入的hMemory,失败的话返回值是NULL。即使内存块在锁定状态,函数仍然可以调用成功,但这时候内存块可能已经被移动了位置,原来用GlobalLock函数获取的指针可能已经失效了,所以调整可移动内存块的大小最好还是先将内存解锁,等调整完毕以后再锁定使用。2.可移动的内存块由于使用可移动的内存块多了一个锁定的动作,速度自然要比使用固定的内存块要慢一点,但固定内存块又存在碎片问题,程序中使用哪种方法有个取舍的问题。如果程序要频繁地分配和释放不定长的内存块,内存的碎片化现象就比较严重,特别是当程序长时间运行时,这种情况下使用可移动内存块比较好。如果程序只进行少量的内存操作,或者虽然频繁分配和释放内存,但使用的内存块长度都是一样的,则使用固定内存块可以节省时间。3.可丢弃的内存块分配可移动内存块的时候需要使用GMEM_MOVEABLE标志,如果同时配合使用GMEM_DISCARDABLE标志的话,这样生成的内存块是可丢弃的内存块。表示当Windows急需内存使用的时候,可以将它从物理内存中丢弃,可丢弃的内存块首先必须是可移动的内存块。3.可丢弃的内存块函数调用如下:invokeGlobalAlloc,GHNDorGMEM_DISCARDABLE,dwBytes.ifeax movhMemory,eax.endif当用GlobalLock锁定内存的时候如果返回NULL指针,表示内存已经被Windows丢弃了。当然其中的数据也丢失了,程序需要重新生成数据。3.可丢弃的内存块另外需要注意的是当内存块被丢弃的时候,内存句柄还是有效的,如果程序还要使用这个句柄,那么可以对它使用GlobalReAlloc函数来重新分配内存。当可丢弃内存块的锁定计数为0时,程序也可以使用GlobalDi
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 5-Phenyl-1-1-3-1-terphenyl-4-4-dicarboxylic-acid-生命科学试剂-MCE
- 大庆中学数学试题及答案
- 2026年高端车内饰定制市场分析报告
- 2026年纺织服装行业品牌竞争与市场发展趋势报告
- 2026年智慧城市建设创新报告:未来展望与应用策略
- 2026年零售行业改制重组与股权架构升级报告
- 2026年麻醉药智能化发展报告
- 2026年健康食品行业报告:懒人经济下健康食品市场的发展趋势
- 2026年大数据产业发展趋势报告:全球洞察与中国实践
- 2026年食品行业烘焙设备技术发展报告
- 2026年新教材人教版九年级上册英语期中复习:Unit 1-4+期中5套测试卷汇编(含答案)
- 河北省邯郸市2025-2026学年高一上学期期中联考语文试题及答案
- ESC 2026新版心力衰竭管理指南精读
- 2024+ESO指南:卵圆孔未闭患者卒中后诊断与治疗
- 2026智联招聘笔试题库及答案
- 2026版《药物临床试验质量管理规范》GCP考试题库(含标准答案+解析)
- EDA技术及应用项目教程 教学大纲
- 中医护理评估
- 2025年度中国展览数据统计报告
- 2026年中考作文押题指导
- 全新版大学英语视听说教程1答案
评论
0/150
提交评论