VirtualAlloc和HeapAlloc有什么区别?

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

那么,他们之间有什么不同呢?

每个API都有不同的用途。 每一个也需要你使用正确的释放/释放function,当你完成了内存。

的VirtualAlloc

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

HeapAlloc

分配你要求的任何内存大小,而不是比VirtualAlloc大的块。 HeapAlloc知道什么时候需要调用VirtualAlloc ,并自动为你做。 像malloc一样,但是只有Windows,并且提供了更多选项。 适合分配一般的内存块。 一些Windows API可能会要求您使用它来分配您传递给它们的内存,或者使用它的伴随HeapFree来释放它们返回给您的内存。

的malloc

C分配内存的方式 如果您使用C而不是C ++编写代码,并希望您的代码能够在Unix计算机上工作,或者有人特别说明您需要使用它,那么请select此选项。 不初始化内存。 适合分配一般的内存块,如HeapAlloc 。 一个简单的API。 使用free释放。 Visual C ++的malloc调用HeapAlloc

C ++分配内存的方式 如果您使用C ++编写,请select此选项。 它也将一个或多个对象放入分配的内存中。 使用delete来释放(或delete[]arrays)。 Visual Studio new调用HeapAlloc ,然后可能会初始化对象,具体取决于您如何调用它。

在最近的C ++标准(C ++ 11及以上版本)中,如果你不得不手动使用delete ,那么你做错了,应该使用一个像unique_ptr这样的智能指针 。 从C ++ 14起,同样可以说是new (用诸如make_unique()函数代替)。


还有一些其他类似的function,如SysAllocString ,你可能会被告知你必须在特定的情况下使用。

VirtualAlloc是操作系统虚拟内存(VM)系统的专门分配。 VM系统中的分配必须以分配粒度(分配粒度)取决于体系结构进行分配。 虚拟机系统中的分配是最基本的内存分配forms之一。 虚拟机的分配可以有多种forms,内存不一定是专用的或物理上支持的RAM(虽然可以)。 VM分配通常是一种特殊用途的分配types,或者是因为分配必须

  • 非常大,
  • 需要共享,
  • 必须根据特定的价值(性能的原因)或
  • 调用者不需要一次使用所有这些内存
  • 等等…

HeapAlloc本质上是mallocnew两者最终调用的。 它被devise成在通用分配的许多不同types的场景下非常快速和可用。 这是古典意义上的“堆”。 堆实际上由VirtualAlloc设置,这是用来从操作系统初始保留分配空间的。 在VirtualAlloc初始化空间之后,configuration各种表,列表和其他数据结构来维护和控制HEAP的操作。 其中一些操作是dynamic调整堆的大小(增长和缩小),使堆适应特定的用途(频繁分配一些大小)等等。

newmalloc有些相同, malloc本质上是一个到HeapAlloc( heap-id-default )的精确调用; new但是,可以[另外]为C ++ 对象configuration分配的内存。 对于给定的对象,C ++将为每个调用者在堆上存储vtables。 这些vtable是redirect的执行和forms的一部分,使C + +它的面向对象的特性,如inheritance,函数重载等…

其他一些常见的分配方法,如_alloca()_alloca() _malloca()是基于堆栈的; FileMappings实际上分配给VirtualAlloc并用指定这些映射的特定位标志设置为FILEtypes。

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

***请注意,由HeapAlloc完成的大内存分配实际上在一定的大小(几百k或16 MB或者我忘记的东西,但相当大:) :)后发运给VirtualAlloc

***编辑我简要地评论了关于IPC和VirtualAlloc ,还有一些相关的VirtualAlloc非常整洁,这个问题的响应者都没有讨论过。

VirtualAlloc Ex是一个进程可以用来在不同进程的地址空间中分配内存的东西。 最典型的情况是,通过CreateRemoteThread (类似于CreateThread ,线程刚刚在另一个进程中运行),在另一个进程的上下文中结合使用。

概述:

  • VirtualAlloc,HeapAlloc等是从操作系统直接分配各种types的内存的Windows API。 VirtualAllocpipe理Windows虚拟内存系统中的页面,而HeapAlloc从特定操作系统堆分配。 坦率地说,你不太可能需要使用任何一个。

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

  • new是一个标准的C ++运算符,它分配内存,然后在内存中正确调用构造函数。 它可以通过malloc或OS API来实现,在这种情况下,它通常也会在应用程序启动时创build一个内存池。

VirtualAlloc ===>在UNIX下的sbrk()

HeapAlloc ====>在UNIX下的malloc()

如果你打算使用需要内存pipe理的语言(比如C或C ++),理解内存分配API(在Windows中)的区别是非常重要的。最好的方法来说明它,恕我直言是用图表:

在这里输入图像说明

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

理解这个图的方法是图中较高的内存分配方法是它使用的较高级的实现。 但是让我们从底部开始。

内核模式内存pipe理器

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

VirtualAlloc / VirtualFree

这些是用户模式下可用的最低级别的 API。 VirtualAlloc函数基本上调用ZwAllocateVirtualMemory ,它依次执行一个快速的系统调用 ring0来进一步处理内核内存pipe理器。 这也是在用户模式下保留/分配所有可用的新内存块的最快方法。

但它有两个主要条件:

  • 它只分配在系统粒度边界上alignment的内存块。

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

那么这个系统粒度是什么? 你可以通过调用GetSystemInfo来获得它。 它作为dwAllocationGranularity参数返回。 它的值是实现(可能是硬件),但在许多64位Windows系统上,它被设置为0x10000字节或64K

所以这意味着,如果你尝试分配一个8字节的内存块,使用VirtualAlloc

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

如果成功, pAddress将在0x10000字节边界上alignment。 即使你只需要8个字节,你将得到的实际内存块也将是整个page (或者像4K字节一样,确切的页面大小是在dwPageSize参数中返回的)。但是,最重要的是,从pAddress跨越0x10000字节(或大多数情况下为64K )的整个内存块将不可用于任何进一步的分配。 所以从某种意义上说,通过分配8个字节,你也可以要求65536。

所以这里的故事的寓意是不要用VirtualAllocreplace应用程序中的通用内存分配。 它必须用于非常特殊的情况,就像下面的堆一样 。 (通常用于保留/分配大块内存。)

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

HeapCreate / HeapAlloc / HeapFree / HeapDestroy

简而言之, 函数基本上是VirtualAlloc函数的一个包装。 这里的其他答案提供了一个相当不错的概念。 我将补充一点,在一个非常简单的观点中, 堆的工作方式是这样的:

  • HeapCreate通过在内部调用VirtualAlloc (或ZwAllocateVirtualMemory是特定的)来保留大量虚拟内存。 它还设置了一个内部数据结构,可以在保留的虚拟内存块中跟踪更小的分配。

  • HeapAllocHeapFree任何调用都不会实际分配/释放任何新的内存(除非该请求超出了HeapCreate已经保留的HeapCreate ),而是通过parsing出来 (或commit )一个先前保留的大块用户请求的更小的内存块。

  • HeapDestroy反过来调用VirtualFree ,它实际上释放了虚拟内存。

所以这一切都使得函数成为应用程序中通用内存分配的最佳select。 对于任意大小的内存分配来说非常好。 但是,为了方便函数而付出的一小笔代价是,在保留较大的内存块时,它们比VirtualAlloc引入了一点点的开销。

堆的另一个好处是你并不需要创build一个。 通常在您的stream程启动时为您创build。 所以可以通过调用GetProcessHeap函数来访问它。

malloc / free

函数的语言特定的包装器。 与HeapAllocHeapFree等不同,这些函数不仅适用于Windows编译代码,也适用于其他操作系统(如Linux等)

如果你使用C语言编程,这是推荐的方法来分配/释放内存(除非你正在编写一个特定的内核模式设备驱动程序)。

新 /删除

作为一个高层次的 (对于C++ )内存pipe理操作员。 它们是特定于C++语言的,就像C malloc一样,也是heap函数的包装器。 他们也有一大堆他们自己的代码,处理构造函数的C++特定初始化,在析构函数中释放等。

如果使用C++编程,这些函数是分配/释放内存和对象的推荐方法。


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

VirtualAlloc =>直接分配到虚拟内存,您保留/提交块。 这对于大型分配很有用,例如大型数组。

HeapAlloc / new =>将内存分配给默认堆(或者您可能创build的任何其他堆)。 这为每个对象分配,对于较小的对象是很好的。 默认堆是可序列化的,因此它具有保证线程分配(这可能会导致高性能场景中的一些问题,这就是为什么你可以创build自己的堆)。

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

简而言之,堆只是一堆由堆pipe理器(而不是原始虚拟内存)pipe理的虚拟内存,

内存世界的最后一个模型是内存映射文件,这种情况对于大块数据(如大文件)来说是很好的。 这在内部使用,当你打开一个EXE(它不加载内存中的EXE,只是创build一个内存映射文件)。