环境:win2000/XP , VC++ 我现在的认识:用VirtualAlloc可分配大容量存贮空间,但可能会用到虚拟内存。而栈中的变 
 量或从堆上分配存贮空间则肯定位于内存中,能够保证最快的访问速度。 请问堆、栈这些内存区域是由谁来构建的,是VC编译器吗?构建代码是不是在crt0.exe中?
 用什么API函数来确保这些区域是分配到了内存上?

解决方案 »

  1.   

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

  2.   


    这是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
      

  3.   

    建议楼主看"windows核心编程"一书,此书讲得很详细。
      

  4.   

    1.没有crt0.exe,有个crt0.c,里面是c语言的运行时代码,它会编译成lib文件,和你的c语言一起编译成exe或dll文件
    2.heap上的内存,不管是new或者malloc出来的,最终都会调用virtualAlloc,这个具体要看运行时是怎么编写了,当然像一楼兄弟说的那样,可以自己重载
    3.stack上的内存,由你指定一个固定大小告诉由编译器。
    例如
    void Foo()
    {
    }
    这个函数啥也不做,就是个例子,debug下展开汇编是00401030 55                   push        ebp
    00401031 8B EC                mov         ebp,esp
    00401033 83 EC 40             sub         esp,40h//看到了吗,这个函数用的stack就是这么多
    00401036 53                   push        ebx
    00401037 56                   push        esi
    00401038 57                   push        edi
    00401039 8D 7D C0             lea         edi,[ebp-40h]
    0040103C B9 10 00 00 00       mov         ecx,10h
    00401041 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
    00401046 F3 AB                rep stos    dword ptr [edi]编译参数会有/stack:XX,是一个固定大小,如果你的函数深层递归,比如自己调用N次,当40h*N(次)>XX,你就属于stack overflow了,明白了吗
      

  5.   

    我个人理解:
      所有的我们应用程序看到的内存都是虚拟内存。所以我们的指针为32bit,理论上有4G内存可以使用。
    而虚拟内存到物理内存的置换(或者说映射)是由操作系统完成的。
    1:而new机制实际上是我们“虚拟内存到物理内存的置换(或者说映射)”算法完全委托给操作系统我们就不管了。
    2:而VirtualAlloc机制我们则可以控制
    “虚拟内存到物理内存的置换(或者说映射)”算法调用的时机,
    什么时候真正提交给物理内存(VirtualAlloc函数的MEM_COMMIT标志),什么时候从物理内存中释放出来VirtualAlloc函数的MEM_DECOMMIT标志)。 
      

  6.   

    谢谢大家的回贴,还有几点想与大家控讨:对于“物理存贮器”,我的认识是不仅仅包括内存条(下面简称之为内存),还可能是位
    于磁盘上的虚拟内存。如果用VirtualAlloc分配不太大的存贮区如数M大小,并要求立即提交物理存贮,我想windows应该把内存空间提交给它吧。但如果用VirtualAlloc请求的空间较大,比如我的内存是512M。而用VirtualAlloc
    请求300M的空间。那windows会全部给内存空间吗?虽然没超过内存最大数量,
    但这几乎会把可用内存资源占个干净,不利于其它进程的运行。windows会不会给一部分
    内存,另一部分用虚拟内存应付?呵呵..说的有点深了,请大家继续指教。
      

  7.   

    不管多大,当你用VirtualAlloc“请求”内存的时候,归根结底到本质上,系统只是去操作了一下页表
    并没有真正的划分出多大的内存给你的进程,所以不会产生占用内存资源过多的现象
      

  8.   

    简单说一下,我对操作系统内存管理的认识,可能有错误,请高手指教,以下情况基于32位体系结构(I32):1 地址空间
    地址空间的实质是可以用做地址的整数的取值范围,
    举例来说:32位体系结构无论物理地址,线性地址还是虚拟地址,都是32位的,那就是说,这3种地址的取值范围只能是0x00000000 ~ 0xFFFFFFFF,一共4G2 物理地址和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的虚拟地址空间。
      

  9.   

    WingForce 的回贴非常深入!但有个地方想再探讨一下:“windows每个进程应该有一个用于维护进程虚拟地址空间的数据结构,VirtualAlloc修改这个数据结构,说明指定的一段整数代表的地址被占用了,不能被再次分配。如此而已 
    VirtualAlloc有两种工作方式,一种是只保留地空间,另一种是除保留地址空间外还提交物理存贮。WingForce所说更像第一种情况。MSDN上说:
    MEM_COMMIT Allocates physical storage in memory or in the paging file on disk for the specified region of memory pages. The function initializes the memory to zero.  
    就是说如果用户请求立即提交物理存贮,则windows可能会从内存或磁盘上拨给存贮。我的看法是只要内存够,windows就分配内存空间。不够才分配磁盘上的虚拟内存(paging file on disk )。如果一个程序用VirtualAlloc以MEM_COMMIT方式申请了太多存贮空间,则必然占用太多内存,使其它程序运行受到不利影响。