Windows下动态内存分配方式

时间:2024-03-24 13:28:36

这里的"动态内存"包含以下两个方面的内容:
  1.内存。这里的"内存"指的是进程的虚拟内存空间。在Win32环境下,每一个进程拥有独立的,大小为4G(0x0000 0000 ~ 0xFFFF FFFF)的虚拟内存空间。
  2.动态。这里的"动态"指的是进程虚拟内存空间中的动态内存区域。在一个进程的虚拟内存空间中,只有动态内存可以在运行是被应用程序*的分配/使用/释放。

在Win32环境下,我们可以使用多种方式来分配/使用/释放动态内存,这些方式包括:
1.Win32 API. 这些API包括VirtualXXX(),HeapXXX(),LocalAlloc(),GlobalAlloc()。
2.C Run-Time Library.这些函数包括malloc(),free()。
3.C++提供的关键词new和关键词delete。

有这么多的内存分配方式,我们在学习和实际项目中编码过程中常常会为使用那种方式而感到迷惑。他们的内部实现是否相同?他们之间有什么本质的区别?他们各自的使用场合又是怎样的? 本文试图通过深入探究他们的本质,为正确理解和使用他们提供一些依据。

首先,我们最好从全局的高度把握他们之间的关系。这里有一张图很好的描述了他们之间的层次关系:Windows下动态内存分配方式

这张图给了我们一个全景,仅从这张图我们就可以清楚地看到他们之间的层次关系:
  第一层:Win32 API作为系统的接口,提供了一组操作虚拟内存的接口;
  第二层:Heap作为虚拟内存的一部分,Win32 API又提供了一组操作Heap内存的接口,但是这些接口是建立在操作虚拟内存的接口的基础上。
  第三层:Windows平台下的C Run-Time Library 又利用Heap API来实现malloc和free。

由此我们可以看出,这些动态内存操作方式之间存有单一的层次关系,位于这个层次的最低层的是Virtual Memory API,可以说这些方式都是建立在Virtual Memory API的基础上。下面就从Virtual Memory API开始,逐层分析他们之间的区别:

一.Virtual Memory API
  作为Windows系统提供的最"核心"的对虚拟内存操作的接口,也作为其他几种方式的基础,Virtual Memory API应该在几种方式中是最通用,也是功能最强大的一种方式。如果想对Virtual Memory API的使用深入的了解,可以参阅《Programming Application for Windows》(By Jeffrey Richter)

二.Heap Memory API
  我们在学习进程内存空间"映象"的时候,也提到了"Heap"这个概念,那个时候"Heap"指的是一段由应用程序在运行时动态分配的内存段(Segment),和其他的内存段(代码段,数据段,栈段等)构成了进程的内存空间。而这里的"Heap"指的是进程拥有的一种对象(Windows中有很多对象,例如WINDOW,ICON,BRUSH),当我们创建一个Heap对象的时候,我们就可以获得这个对象的Handle,然后我们就可以使用这个handle来使用动态内存,最后销毁这个对象。

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

四.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中释放时,问题就出现了。

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

  CMyObject* pObj = new CMyObject;

 

编译器会执行以下两个任务:
1。在堆上动态分配必要的内存。这个任务是由编译器提供的一个全局函数void* ::operator new(size_t)来完成的。值得注意的是任何一个类都可以重载这个全局函数。如果类重载了这个函数的化,被类重载的那个会被调用。
2。调用CMyClass的构造函数来初始化刚刚生成的对象。当然如果分配的对象是C++中的基本数据类型则不会有构造函数调用。
如果要深入全局函数void* ::operator new(size_t)的话,我们会发现,它的具体实现是通过调用malloc来分配内存的。

有了这样的分析,我们对这些动态内存分配方式有了一个更高一级的认识,在我们的代码中就可以正确使用他们。

参考文献:
1.《Programming Application for Windows》(By Jeffrey Richter)
2.《Windows System Programming Third Edition》(By  Johnson M. Hart)
3. 这里是一篇从"历史"的角度分析LocalAlloc和GlobalAlloc的区别的文章:http://blogs.msdn.com/oldnewthing/archive/2004/11/01/250610.aspx

21/10/2006 于家中

v1.1
1。修改了文中关于关键词new/delete的内容,以前把关键词new和函数void* operator new(size_t)混淆了。

19/11/2006 于家中

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1344011