首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > VC/MFC >

请问内存管理的有关问题

2012-01-20 
请教内存管理的问题环境:win2000/XP , VC++我现在的认识:用VirtualAlloc可分配大容量存贮空间,但可能会用

请教内存管理的问题
环境:win2000/XP , VC++

 我现在的认识:用VirtualAlloc可分配大容量存贮空间,但可能会用到虚拟内存。而栈中的变 
 量或从堆上分配存贮空间则肯定位于内存中,能够保证最快的访问速度。

 请问堆、栈这些内存区域是由谁来构建的,是VC编译器吗?构建代码是不是在crt0.exe中?
 用什么API函数来确保这些区域是分配到了内存上?

[解决办法]
1、内存分配是由编译器来管理的。
2、假如LZ开发的时候,动态创建了一个比较大的内存缓冲区(10M),那么,LZ可以强制让栈或堆内存就使用这10M的内存,
如果没有可用的内存,可以再申请更大的或者报错。这个需要重载operator new和operator delete以及operator new[]和operator delete[],这样就可以保证分配的内存大小就在LZ指定的范围之内(内存地址范围之内)
[解决办法]

这是windows核心编程上的原话:

堆栈可以用来分配许多较小的数据块。例如,若要对链接表和链接树进行管理,最好的方法是使用堆栈,而不是第1 5章介绍的虚拟内存操作方法或第1 7章介绍的内存映射文件操作方法。堆栈的优点是,可以不考虑分配粒度和页面边界之类的问题,集中精力处理手头的任务。堆栈的缺点是,分配和释放内存块的速度比其他机制要慢,并且无法直接控制物理存储器的提交和回收。

从内部来讲,堆栈是保留的地址空间的一个区域。开始时,保留区域中的大多数页面没有被提交物理存储器。当从堆栈中进行越来越多的内存分配时,堆栈管理器将把更多的物理存储器提交给堆栈。物理存储器总是从系统的页文件中分配的,当释放堆栈中的内存块时,堆栈管理器将收回这些物理存储器。

M i c r o s o f t并没有以文档的形式来规定堆栈释放和收回存储器时应该遵循的具体规则,Windows 98 与Windows 2000的规则是不同的。可以这样说,Windows 98 更加注重内存的使用,因此只要可能,它就收回堆栈。Windows 2000更加注重速度,因此它往往较长时间占用物理存储器,只有在一段时间后页面不再使用时,才将它返回给页文件。M i c r o s o f t常常进行适应性测试并运行各种不同的条件,以确定在大部分时间内最适合的规则。随着使用这些规则的应用程序和硬件的变更,这些规则也会有所变化。如果了解这些规则对你的应用程序非常关键,那么请不要使用堆栈。相反,可以使用虚拟内存函数(即Vi r t u a l A l l o c和Vi r t u a l F r e e),这样,就能够控制这些规则。



我的理解:
实际上即使是你这样写
//分配700M内存,实际你的物理内存只有512M,也是可以成功的。
BYTE *p = new BYTE [700 * 1024 * 1024];
为什么因为实际上你new 内存的时候,它只是返回保留区域中的页面文件(也就是虚拟内存),并不是真正要占用物理内存。
所以new 分配和VirtualAlloc分配实际上是一样的。
他们真正不同的是:
VirtualAlloc分配机制你可以明确的控制什么时候虚拟内存要提交到物理内存(VirtualAlloc函数的MEM_COMMIT标志),什么时候又应该释放物理存储器而不释放该虚拟内存区域(VirtualAlloc函数的MEM_DECOMMIT标志)。
而new 机制是不能控制这个的。它由操作系统自己的算法决定。

结论:
如果你想控制虚拟内存提交给物理内存 和 释放相应物理内存(不释放虚拟内存区域)。那么请你用VirtualAlloc.
否则请用new



[解决办法]
1.没有crt0.exe,有个crt0.c,里面是c语言的运行时代码,它会编译成lib文件,和你的c语言一起编译成exe或dll文件
2.heap上的内存,不管是new或者malloc出来的,最终都会调用virtualAlloc,这个具体要看运行时是怎么编写了,当然像一楼兄弟说的那样,可以自己重载
3.stack上的内存,由你指定一个固定大小告诉由编译器。
例如
void Foo()
{
}
这个函数啥也不做,就是个例子,debug下展开汇编是

Assembly code
00401030 55                   push        ebp00401031 8B EC                mov         ebp,esp00401033 83 EC 40             sub         esp,40h//看到了吗,这个函数用的stack就是这么多00401036 53                   push        ebx00401037 56                   push        esi00401038 57                   push        edi00401039 8D 7D C0             lea         edi,[ebp-40h]0040103C B9 10 00 00 00       mov         ecx,10h00401041 B8 CC CC CC CC       mov         eax,0CCCCCCCCh00401046 F3 AB                rep stos    dword ptr [edi]
[解决办法]
简单说一下,我对操作系统内存管理的认识,可能有错误,请高手指教,以下情况基于32位体系结构(I32):

1 地址空间
地址空间的实质是可以用做地址的整数的取值范围,
举例来说:32位体系结构无论物理地址,线性地址还是虚拟地址,都是32位的,那就是说,这3种地址的取值范围只能是0x00000000 ~ 0xFFFFFFFF,一共4G

2 物理地址和MMIO
32位体系结构的32位地址总线,允许cpu寻址32位物理地址。但是访问其他硬件的IO操作也需要占用部分地址空间,即:比如访问显存的时候,体系结构将0xF5000000 ~ 0xF5FFFFFF这段整数作为显存的地址占用了,所以实际可以使用的物理地址并没有4G(一般是3G,还有很小的一部分地址空间用于和16位体系结构的兼容性考虑)。这叫做Memory-mapped I/O。

3 MMU,逻辑地址,线性地址,物理地址 --段页式内存管理
保护模式下,MMU将逻辑地址映射为物理地址,一般来说,过程如下:
逻辑地址-->线性地址:逻辑地址以[段基址:偏移量]的形式存在,使用段寄存器中保存的选择子为偏移,从GDT(Global Descriptor Table全局描述符表)中获取段基址,段基址开始偏移指定的偏移量即获得相应的线性地址
线性地址-->物理地址:以线性地址高10位为偏移量查找PDE(Page Directory Entry页目录表)获得对应的PTE(Page Table Entry页表)首地址,以跟着的10位为偏移量查找PTE,获得物理页的首地址,以最后的低12位从物理页首地址开始偏移获得物理地址。
在这个查找过程中,如果出现段越界或者查找页表失败,则会产生访问违例的中断,给系统一个纠正错误的机会。使用虚拟内存的操作系统,会捕捉这个中断。一般来说,发生这个中断就说明该页不存在于物理内存中,那么查找磁盘上的虚拟内存交换文件,如果在文件查找到该页,则设法把该页调入物理内存,如果没有可用的物理内存,则根据某种算法,将某一物理页交换到磁盘文件上。

4 windows进程的内存地址空间
如果没记错的话,windows没有使用分段。每个进程都有4G的虚拟地址空间(用户只能使用其中的2G,其他部分被操作系统占用了)。
进程获得的内存其实都是逻辑地址或者说虚拟地址,虚拟地址中连续的几个页,他们在实际的物理地址空间上不一定连续,依靠MMU的映射机制,可以将任意的几个物理页映射成连续的一段虚拟地址。


所以,我认为VirtualAlloc的时候,只是让操作系统从当前进程的虚拟地址空间的0x00000000 ~ 0xFFFFFFFF(实际上只有2G不到)这么多整数中保留一段值,也就是说,windows每个进程应该有一个用于维护进程虚拟地址空间的数据结构,VirtualAlloc修改这个数据结构,说明指定的一段整数代表的地址被占用了,不能被再次分配。如此而已。。。
接下来,当进程实际上访问这段逻辑地址时,如前面所说,产生了一个中断,于是windows捕捉了这个中断,并且实际分配一个物理页给改进程,于是现在可以访问内存了。VirtualAlloc很大的内存,比如1G,也是一样,如果不访问这1G内存的某一个实际地址,系统也许永远也不会给他分配实际的物理内存。甚至,实际上只有1页物理内存,也可以通过将这一页不断的与磁盘上的页面文件进行交换,访问1G的虚拟地址空间。

热点排行