VirtualAlloc和HeapAlloc有什么区别?


81

有很多方法在Windows环境中分配内存,如VirtualAllocHeapAllocmallocnew

因此,它们之间有什么区别?

Answers:


82

每个API都有不同的用途。完成内存处理后,每一项都要求您使用正确的释放/释放功能。

虚拟分配

低级Windows API,提供了很多选项,但主要用于相当特定情况下的人们。只能在较大的块中分配内存(编辑:不是4KB)。在某些情况下您需要它,但是当您处于这些情况之一时,您就会知道。最常见的一种是您是否必须直接与另一个进程共享内存。不要将其用于通用内存分配。使用VirtualFree解除分配。

堆Alloc

分配所需的任何内存大小,而不是VirtualAllocHeapAlloc知道何时需要呼叫VirtualAlloc,并自动为您呼叫。与相似malloc,但仅适用于Windows,并提供了更多选择。适用于分配常规内存块。某些Windows API可能会要求您使用它来分配传递给它们的内存,或者使用它的同伴HeapFree释放它们返回给您的内存。

分配

分配内存的C方法。如果您是用C而不是C ++编写的,并且您希望代码也可以在Unix计算机上工作,或者有人专门说您需要使用它,则最好使用此方法。不初始化内存。适用于分配一般的内存块,例如HeapAlloc。一个简单的API。使用free解除分配。Visual C ++的malloc调用HeapAlloc

分配内存的C ++方法。如果您使用C ++编写,请首选此选项。它将一个或多个对象也放入分配的内存中。使用delete解除分配(或delete[]数组)。Visual Studionew调用HeapAlloc,然后根据调用方式初始化对象。

在最近的C ++标准(C ++ 11及以上),如果你必须手动使用delete,你这样做是错误的,应该使用智能指针unique_ptr代替。从C ++ 14开始,可以这样说new(用代替了诸如make_unique())。


还有其他一些类似的功能,例如SysAllocString,可能会告诉您必须在特定情况下使用。


18
道格:VirtualAlloc并不严格限于4kb分配,它是GetSystemInfo(),SYSTEM_INFO :: dwAllocationGranularity返回的大小。实际上很少是4kb。在我的主机上,它是64k,我怀疑您也一样。x86 ABI中各种描述符表的最小页面大小条目为4KB。4KB是可以独立允许的最小大小,R / W / X,但是对VirtualAlloc没有任何意义。如果您参考VirtualAlloc文档,则还有LARGE_PAGES选项(请参阅msdn.microsoft.com/zh-cn/library/aa366568(VS.85).aspx)。
RandomNickName42

您知道DirectShow为什么使用VirtualAlloc为媒体分配内存缓冲区而不使用malloc吗?
Aviad Rozenhek

Aviad:DirectShow使用虚拟分配来保留内存,以便它可以传递性能优化所需的标志,诸如不可分页之类的东西,或者它可以保留物理页面,如果您的硬件支持,则可以提高性能。
RandomNickName42

8
@ RandomNickName42:这是不对的。分配起始地址始终与粒度(64KB)对齐,但是分配长度四舍五入为页面大小(4KB)。实际上,地址空间以64KB块保留,但以4KB块提交。起始地址的对齐通常没有那么有趣,因此从大多数角度来看,VirtualAlloc都以4KB块的形式工作。仅当您尝试执行许多小型VirtualAllocs(地址空间用完)或某些奇特的事情(例如相邻但单独的分配)时,这些详细信息才变得很重要。
Arx 2013年

我以为malloc / new / CRT叫做HeapAlloc,然后使用它自己的算法从HeapAlloc内存块返回内存块?
paulm

29

VirtualAlloc是OS虚拟内存(VM)系统的专门分配。VM系统中的分配必须以与架构相关的分配粒度(分配粒度)进行。VM系统中的分配是内存分配的最基本形式之一。VM分配可以采用多种形式,内存不一定专用或物理支持在RAM中(尽管可以)。VM分配通常是一种特殊用途的分配类型,因为分配必须

  • 很大
  • 需要共享
  • 必须根据特定值(性能原因)对齐,或
  • 呼叫者不必立即使用所有这些内存...
  • 等等...

HeapAlloc本质上是什么mallocnew最终都被称为。它被设计为在通用分配的许多不同类型的场景下非常快速且可用。它是经典意义上的“堆”。堆实际上是由设置的VirtualAlloc,用于从OS最初保留分配空间。通过初始化空间之后VirtualAlloc,将配置各种表,列表和其他数据结构以维护和控制HEAP的操作。其中一些操作的形式是动态调整堆大小(增大和缩小),使堆适应特定的用法(某些大小的频繁分配)等。

new并且malloc有些相同,malloc实际上是对HeapAlloc( heap-id-default );的精确调用。new但是,可以[另外]配置C ++对象分配的内存。对于给定的对象,C ++将为每个调用者将vtable存储在堆中。这些vtables是执行的重定向,是C ++ OO特性的一部分,如继承,函数重载等。

其他一些常见的分配方法,如_alloca()_malloca()为主; FileMappings实际上是分配有VirtualAlloc特定位标志并设置的,这些标志将这些映射指定为type FILE

在大多数情况下,您应该以与该内存使用一致的方式分配内存;)。 new在C ++中,malloc对于C,VirtualAlloc适用于大规模或IPC案例。

***请注意,由HeapAlloc进行的大内存分配实际上是VirtualAlloc在一定大小(几百k或16 MB或我忘记的东西,但相当大的:)之后运到的。

***编辑我简短地评论了IPC和VirtualAlloc,与VirtualAlloc此相关的内容也很简洁,对此问题的响应者都没有讨论过。

VirtualAlloc防爆就是一个进程可以使用的地址空间分配内存不同的过程。最典型的是,它结合使用通过CreateRemoteThread在另一个进程的上下文中获得远程执行(类似于CreateThread,该线程仅在另一个进程中运行)。


28

如果计划使用需要内存管理的语言(例如C或C ++),则了解内存分配API(在Windows中)之间的区别非常重要。IMHO的最佳说明方法是使用图表:

在此处输入图片说明

请注意,这是一个非常简化的Windows特定视图。

理解该图的方式是,在内存分配方法在图上越高,它使用的实现级别越高。但是,让我们从底部开始。

内核模式内存管理器

它提供了操作系统的所有内存保留和分配,以及对内存映射文件共享内存写时复制操作等的支持。无法从用户模式代码中直接访问它,因此我将跳过在这里。

VirtualAlloc /虚拟免费

这些是用户模式下可用的最低级别的API 。该函数基本上会调用ZwAllocateVirtualMemory,后者随后会执行快速syscall来将进一步的处理委托给内核内存管理器。这也是从用户模式下所有可用内存中保留/分配新内存块的最快方法。VirtualAllocring0

但是它具有两个主要条件:

  • 它仅分配在系统粒度边界上对齐的内存块。

  • 它仅分配大小为系统粒度倍数的内存块。

那么,系统粒度是多少?您可以通过调用GetSystemInfo获得它。它作为dwAllocationGranularity参数返回。它的值特定于实现(可能还包括硬件),但是在许多64位Windows系统上,其值设置为0x10000字节或64K

所以这意味着,如果您尝试分配,请说一个8字节的内存块VirtualAlloc

void* pAddress = VirtualAlloc(NULL, 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

如果成功,pAddress将在0x10000字节边界对齐。即使您只请求了8个字节,您将获得的实际存储块将是整个存储块page(或类似4K字节的块。确切的页面大小将在dwPageSize参数中返回。)但是,最重要的是,整个存储块跨越0x10000字节(或64K大多数情况下)的字节pAddress 将不再可用于任何其他分配。因此,从某种意义上讲,分配8个字节就可以要求65536。

因此,这里的故事的寓意不是VirtualAlloc在您的应用程序中替代通用内存分配。它必须用于非常特殊的情况,如下面的所示。(通常用于保留/分配大块内存。)

VirtualAlloc错误使用会导致严重的内存碎片。

HeapCreate / HeapAlloc / HeapFree / HeapDestroy

简而言之,函数基本上是VirtualAlloc函数的包装器。这里的其他答案提供了一个很好的概念。我将在一个非常简单的视图中添加,堆的工作方式如下:

  • HeapCreate通过VirtualAlloc内部调用(或ZwAllocateVirtualMemory具体来说)来保留大量的虚拟内存。它还建立了一个内部数据结构,该结构可以跟踪保留的虚拟内存块中其他更小的大小分配。

  • 任何对调用的请求HeapAllocHeapFree不会实际分配/释放任何新的内存(除非请求当然超出了已在其中保留的内容HeapCreate),而是通过将其分解为较小的内存块来计量(或commit)先前保留的大块用户要求。

  • HeapDestroy依次调用VirtualFree实际上释放虚拟内存。

因此,所有这些使函数成为应用程序中通用内存分配的理想候选者。这对于任意大小的内存分配非常有用。但是,为方便使用函数而付出的代价很小,就是VirtualAlloc在保留较大的内存块时,它们会带来一些开销。

堆的另一个好处是,您实际上不需要创建。通常在过程开始时为您创建。因此,可以通过调用GetProcessHeap函数来访问它。

malloc /免费

函数的特定于语言的包装器。与HeapAllocHeapFree等不同,这些功能不仅在您的代码针对Windows编译时有效,而且对于其他操作系统(例如Linux等)也适用。

如果您使用C进行编程,则这是建议的分配/释放内存的方法。(除非您正在编码特定的内核模式设备驱动程序。)

新增/删除

作为高级(很好C++)的内存管理操作员来。他们是为特定的C++语言,像mallocC,也可用于包装器heap的功能。他们还有一堆自己的代码,这些代码处理C++特定于构造函数的初始化,析构函数中的释放,引发异常等。

如果在中编程,建议使用这些功能分配/释放内存和对象C++


最后,我想对其他答复中关于使用VirtualAlloc进程间共享内存的内容发表评论。VirtualAlloc本身不允许与其他进程共享其保留/分配的内存。为此,需要使用CreateFileMapping可以创建可与其他进程共享的命名虚拟内存块的API。它还可以将磁盘上的文件映射到虚拟内存中以进行读/写访问。但这是另一个话题。


7

在大纲中:

  • VirtualAlloc,HeapAlloc等是Windows API,它们直接从OS分配各种类型的内存。VirtualAlloc管理Windows虚拟内存系统中的页面,而HeapAlloc从特定的OS堆进行分配。坦白说,您不太可能需要使用其中的任何一种。

  • malloc是一个标准C(和C ++)库函数,可为您的进程分配内存。应用程序启动时,malloc的实现通常会使用一种OS API创建一个内存池,然后在发出malloc请求时从中分配内存

  • new是一个标准C ++运算符,它分配内存,然后在该内存上适当地调用构造函数。它可以根据malloc或OS API来实现,在这种情况下,它通常也将在应用程序启动时创建一个内存池。



2

VirtualAlloc=>直接分配到虚拟内存中,以块为单位进行保留/提交。这对于大型分配(例如大型数组)非常有用。

HeapAlloc/ new=>在默认堆(或您可能创建的任何其他堆)上分配内存。这为每个对象分配,非常适合较小的对象。默认堆是可序列化的,因此它可以保证线程分配(这可能会在高性能方案中引起一些问题,因此您可以创建自己的堆)。

malloc=>使用C运行时堆,类似于,HeapAlloc但在兼容性场景中很常见。

简而言之,堆只是由堆管理器控制的虚拟内存块(而不是原始虚拟内存)

内存世界上的最后一个模型是内存映射文件,这种情况适用于大数据块(如大文件)。当您打开EXE时在内部使用它(它不会将EXE加载到内存中,而只是创建一个内存映射文件)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.