[windows] windows平台下的内存管理

1. Windows平台下主要的内存管理途径

申请

释放

new

delete

malloc

free

CoTaskMemAlloc

CoTaskMemFree

IMalloc::alloc

IMalloc/free

GlobalAlloc

GlobalFree

LocalAlloc

LocalFree

HeapAlloc

HeapFree

VirtualAlloc

VirtualFree

. 调用关系

image

第一层:Win32 API作为系统的接口,提供了一组操作虚拟内存的接口;

第二层:Heap作为虚拟内存的一部分,Win32 API又提供了一组操作Heap内存的接口,但是这些接口是建立在操作虚拟内存的接口的基础上。

第三层:Windows平台下的C Run-Time Library 又利用Heap API来实现malloc和free。

由此我们可以看出,这些动态内存操作方式之间存有单一的层次关系,位于这个层次的最低层的是Virtual Memory API,可以说这些方式都是建立在Virtual Memory API的基础上。

调用关系如下表所示为 : new -> malloc -> HeapAlloc -> VirtualAlloc -> 驱动程序的_PageAlloc

调用者 被调用者

msvcrt.malloc

kernel32.HeapAlloc(ntdll.RtlAllocateHeap)

kernel32.LocalAlloc

ntdll.RtlAllocateHeap

kernel32.GlobleAlloc

ntdll.RtlAllocateHeap

kernel32.HeapAlloc

ntdll.RtlAllocateHeap(映射)

kernel32.VirtualAlloc

kernel32.VirtualAllocEx

kernel32.VirtualAllocEx

ntdll.NtAllocateVirtualMemory

ntdll.RtlAllocateHeap

ntdll.NtAllocateVirtualMemory

ntdll.NtAllocateVirtualMemory

ntdll.KiFastSystemCall

ntdll.KiFastSystemCall

sysenter指令 (0F34)

3. 方法解析

3.1 Virtual Memory API

    作为Windows系统提供的最"核心"的对虚拟内存操作的接口,也作为其他几种方式的基础,Virtual Memory API应该在几种方式中是最通用,也是功能最强大的一种方式。在Windows里内存管理是分为两部份,全局内存是系统管理的内存,因而所有进程都可以访问的内存,而每一个进程又有自己的内存空间,这就是虚拟内存空 间了,而虚拟内存的空间比较大,当物理内存不足时,系统会把虚拟内存的数据保存到硬盘里,这样只要硬盘的空间足够大,每个进程就可以使用3G的内存。虚拟内存分配可以作为程序里分配内存的主要方式,比如大量的数据缓冲区,动态分配内存的空间。使用VirtualAlloc函数来分配内存的速度要比全局内存要快。

   1:   LPVOID  WINAPI  VirtualAlloc( __in_opt LPVOID lpAddress, __in  SIZE_T dwSize,  __in   DWORD flAllocationType,   __in  DWORD flProtect );

 

lpAddress是指定内存开始的地址。

dwSize是分配内存的大小。

flAllocationType是分配内存的类型。

flProtect是访问这块分配内存的权限。

   1:  void MemVirtual(void) {
   2:      //分配新内存大小。
   3:      UINT nNewSize = (UINT) ceil(1500 / 1024.0) * 1024;
   4:      PBYTE pNewBuffer = (PBYTE) VirtualAlloc(NULL,nNewSize,MEM_COMMIT,PAGE_READWRITE);
   5:      if (pNewBuffer){
   6:         //测试虚拟内存。
   7:         ZeroMemory(pNewBuffer,1500);
   8:         memcpy(pNewBuffer,_T("分配虚拟内存成功\r\n"),sizeof(_T("分配虚拟内存成功\r\n")));
   9:         OutputDebugString((LPWSTR)pNewBuffer);
  10:         //释放分配的内存,第三个参数一定是MEM_RELEASE
  11:         VirtualFree(pNewBuffer,0,MEM_RELEASE);
  12:      }
  13:  }

3.2 Heap Memory API

在进程私有的内存空间里分配里,有两种分配情况,一种上基于栈式的内存分配,另一种是基于堆内存的分配。使用堆内存分配是使用HeapAlloc函数来实现的,也就是实现new操作符分配内存时会调这个函数。这里的"Heap"指的是进程拥有的一种对象(Windows中有很多对象,例如WINDOW,ICON,BRUSH),当我们创建一个Heap对象的时候,我们就可以获得这个对象的Handle,然后我们就可以使用这个handle来使用动态内存,最后销毁这个对象。

   1:  LPVOID WINAPI HeapAlloc(__in HANDLE hHeap,__in DWORD dwFlags,__in SIZE_T dwBytes);

 

hHeap是进程堆内存开始位置。
dwFlags是分配堆内存的标志。
dwBytes是分配堆内存的大小。

   1:  void MemHeap(void){
   2:      const int nHeapSize = 1024;
   3:      PBYTE pNewHeap = (PBYTE) ::HeapAlloc(GetProcessHeap(), 0, nHeapSize);
   4:      if (pNewHeap){
   5:        //测试分配堆内存。
   6:        ZeroMemory(pNewHeap,nHeapSize);
   7:        memcpy(pNewHeap,_T("分配堆内存成功\r\n"),sizeof(_T("分配堆内存成功\r\n")));
   8:        OutputDebugString((LPWSTR)pNewHeap);
   9:        //释放内存
  10:        BOOL bRes = ::HeapFree(GetProcessHeap(), 0, pNewHeap);
  11:        if (bRes != TRUE){
  12:              OutputDebugString(_T("释放内存出错\r\n"));
  13:          }
  14:      }
  15:  }

3.3 LocalAlloc/GlobalAlloc

这两个函数是Win16 API中遗留下来的两个函数,Win32 API为了保持兼容性才包含了这两个函数。这两个函数内部是通过Heap Memory API来操作一个"特殊"的Heap对象:进程的默认堆对象。每一个进程在初始化的时候,都会创建一个默认的Heap对象,在进程结束的时候销毁这个默认 的Heap对象。LocalAlloc和GlobalAlloc的区别仅表现在Win16环境下,在Win16环境下,内存的地址是通过段:段内偏移量 来获取的,LocalAlloc()只能在同一段内分配内存,而GlobalAlloc可以跨越段边界访问内存。 在Win32环境下内存访问不存在这样的限制,所以他们表现出相同的功能。由于Heap Memory API完全可以实现他们两个的功能,所以在Win32下不推荐使用这两个函数。

在Windows系统里,有一项功能非常实用,就是剪贴板功能,它能够从一个程序里与另一个程序进行数据交换的功能,也就是说两个进程上是可以共享数据。要实现这样的功能,Windows系统在底层上有相应的支持,就是高端地址的内存是系统内存,这样就可以不同的进程进行共享数据了。因此,调用函数GlobalAlloc来分配系统内存,让不同的进程实现共享数据,也就是剪贴板功能,可以在一个进程内分配内存,在另一个进程里访问数据后删除内存。

 

   1:  HLOCAL WINAPI LocalAlloc(__in UINT uFlags,__in SIZE_T uBytes);
   2:  HGLOBAL WINAPI GlobalAlloc (__in UINT uFlags, __in SIZE_T dwBytes);
示例代码:
   1:  void MemGlobal(void) {
   2:      //分配全局内存。
   3:      BYTE* pGlobal = (BYTE*)::GlobalAlloc(GMEM_FIXED,1024);
   4:      if (!pGlobal) {
   5:          return;
   6:      } else {
   7:          //测试全局内存
   8:          ZeroMemory(pGlobal,1024);
   9:          memcpy(pGlobal,_T("分配内存成功\r\n"),sizeof(_T("分配内存成功\r\n")));
  10:          OutputDebugString((LPWSTR)pGlobal);
  11:      }
  12:      //释放全局内存。
  13:      ::GlobalFree((HGLOBAL)pGlobal);
  14:  }

3.4 malloc/free

 

     这两个函数是使用频率最高的两个函数,由于他们是标准C库中的一部分,所以具有极高的移植性。这里的"移植性"指的是使用他们的代码可以在不同的平台下编 译通过,而不同的平台下的C Run-Time Library的具体实现是平台相关的,在Windows平台的C Run-Time Library中的malloc()和free()是通过调用Heap Memory API来实现的。值得注意的是C Run-Time Library拥有独立的Heap对象,我们知道,当一个应用程序初始化的时候,首先被初始化的是C Run-Time Library,然后才是应用程序的入口函数,而Heap对象就是在C Run-Time Library被初始化的时候被创建的。

      对于动态链接的C Run-Time Library,运行库只被初始化一次,而对于静态连接的运行库,每链接一次就初始化一次,所以对于每个静态链接的运行库都拥有彼此不同的Heap 对象。这样在某种情况下就会出问题,导致程序崩溃,例如一个应用程序调用了多个DLL,除了一个DLL外,其他的DLL,包括应用程序本身动态连接运行 库,这样他们就使用同一个Heap对象。而有一个DLL使用静态连接的运行库,它就拥有一个和其他DLL不同的Heap 对象,当在其他DLL中分配的内存在这个DLL中释放时,问题就出现了。

3.5 关键词new/关键词delete

     这两个词是C++内置的关键词(keyword)。当C++编译器看到关键词new的时候,例如:

     CMyObject* pObj = new CMyObject;

     编译器会执行以下两个任务:

    a) 在堆上动态分配必要的内存。这个任务是由编译器提供的一个全局函数void* ::operator new(size_t)来完成的。值得注意的是任何一个类都可以重载这个全局函数。如果类重载了这个函数的化,被类重载的那个会被调用。

    b) 调用CMyObject的构造函数来初始化刚刚生成的对象。当然如果分配的对象是C++中的基本数据类型则不会有构造函数调用。

如果要深入全局函数void* ::operator new(size_t)的话,我们会发现,它的具体实现是通过调用malloc来分配内存的,而在win平台下,malloc最终调用的是HeapAlloc方法。

3.6 CoTaskMemAlloc /IMalloc

     CoTaskMemAlloc用于COM对象,它在进程的缺省堆中分配内存。

     IMalloc接口是对 CoTaskMemAlloc/CoTaskMemFree 的再次封装。

posted @ 2011-03-26 17:42 zsounder 阅读(...) 评论(...) 编辑 收藏