【发布时间】:2025-12-09 08:40:01
【问题描述】:
Windows环境下分配内存的方法有很多,如VirtualAlloc、HeapAlloc、malloc、new。
那么,它们之间有什么区别呢?
【问题讨论】:
标签: winapi memory heap-memory new-operator
Windows环境下分配内存的方法有很多,如VirtualAlloc、HeapAlloc、malloc、new。
那么,它们之间有什么区别呢?
【问题讨论】:
标签: winapi memory heap-memory new-operator
每个 API 都有不同的用途。每一个还要求您在使用完内存后使用正确的释放/释放功能。
一种低级的 Windows API,它提供了许多选项,但主要对处于相当特定情况的人有用。只能在(编辑:不是 4KB)更大的块中分配内存。在某些情况下您需要它,但是当您处于其中一种情况时您会知道。最常见的一种情况是您必须直接与另一个进程共享内存。不要将其用于通用内存分配。使用VirtualFree 解除分配。
分配您要求的任何大小的内存,而不是比VirtualAlloc 大的块。 HeapAlloc 知道何时需要调用 VirtualAlloc 并自动为您调用。与 malloc 类似,但仅适用于 Windows,并提供了更多选项。适合分配一般的内存块。某些 Windows API 可能要求您使用它来分配您传递给它们的内存,或使用它的伴侣 HeapFree 来释放它们返回给您的内存。
分配内存的C方式。如果您使用 C 而不是 C++ 编写,并且希望您的代码可以在例如Unix 计算机也是如此,或者有人特别说您需要使用它。不初始化内存。适用于分配一般内存块,如HeapAlloc。一个简单的 API。使用free 解除分配。 Visual C++ 的malloc 调用HeapAlloc。
C++ 分配内存的方式。如果您使用 C++ 编写,则更喜欢这个。它也将一个或多个对象放入分配的内存中。使用delete 释放(或delete[] 用于数组)。 Visual Studio 的 new 调用 HeapAlloc,然后可能会初始化对象,具体取决于您如何调用它。
在最近的 C++ 标准(C++11 及更高版本)中,如果您必须手动使用 delete,那么您做错了,应该使用像 unique_ptr 这样的 智能指针 .从 C++14 开始,new 也可以这样说(替换为 make_unique() 等函数)。
还有一些其他类似的功能,例如SysAllocString,可能会告诉您必须在特定情况下使用。
【讨论】:
如果您打算使用需要内存管理的语言(如 C 或 C++),了解内存分配 API(在 Windows 中)之间的区别非常重要。恕我直言,最好的说明方法是使用图表:
请注意,这是一个非常简化的特定于 Windows 的视图。
理解这个图的方法是,内存分配方法在图上越高,它使用的更高级别实现。但让我们从底层开始。
它为操作系统提供所有内存预留和分配,以及对内存映射文件、共享内存、写时复制的支持 操作等。它不能从用户模式代码直接访问,所以我将在此处跳过。
这些是user mode 提供的最低级别 API。 VirtualAlloc 函数基本上调用了ZwAllocateVirtualMemory,而ZwAllocateVirtualMemory 又快速syscall 到ring0 以将进一步的处理委托给内核内存管理器。这也是在用户模式下从所有可用内存中保留/分配新内存块的最快方法。
但它有两个主要条件:
它只分配在系统粒度边界上对齐的内存块。
它只分配大小为系统粒度倍数的内存块。
那么这个系统粒度是什么?您可以致电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 参数中返回。)但是,最重要的是,来自pAddress的跨越0x10000 字节(或在大多数情况下为64K)的整个内存块将不可用于任何进一步的分配。所以从某种意义上说,通过分配 8 个字节,你也可以要求 65536。
所以这里的故事的寓意是不要用VirtualAlloc 代替应用程序中的通用内存分配。它必须用于非常特殊的情况,就像下面的 heap 一样。 (通常用于保留/分配大块内存。)
错误地使用VirtualAlloc会导致严重的内存碎片。
简而言之,heap 函数基本上是VirtualAlloc 函数的包装器。这里的其他答案提供了一个很好的概念。我将补充一点,在一个非常简单的视图中,heap 的工作方式是这样的:
HeapCreate 通过在内部调用VirtualAlloc(或具体来说是ZwAllocateVirtualMemory)保留一大块虚拟内存。它还建立了一个内部数据结构,可以在保留的虚拟内存块内跟踪更小的大小分配。
commit)之前保留的大块,将其分解为用户请求的较小内存块。
HeapDestroy 依次调用 VirtualFree,这实际上释放了虚拟内存。
因此,所有这些都使 heap 函数成为应用程序中通用内存分配的完美候选者。它非常适合任意大小的内存分配。但是为了 heap 函数的便利性而付出的一个小代价是,当保留更大的内存块时,它们会比VirtualAlloc 带来轻微的开销。
heap 的另一个好处是您实际上并不需要创建一个。它通常是在您的流程开始时为您创建的。所以可以通过调用GetProcessHeap函数来访问它。
是 heap 函数的特定语言包装器。与HeapAlloc、HeapFree等不同,这些函数不仅适用于Windows编译的代码,也适用于其他操作系统(如Linux等)
如果您使用 C 编程,这是分配/释放内存的推荐方法。(除非您正在编写特定的内核模式设备驱动程序。)
作为一个高级(好吧,对于C++)内存管理操作员。它们特定于C++ 语言,就像malloc 的C 一样,也是heap 函数的包装器。他们也有一大堆自己的代码来处理 C++ 特定的构造函数初始化、析构函数中的释放、引发异常等。
如果您在C++ 中编程,推荐使用这些函数分配/释放内存和对象。
最后,我想对其他回复中关于使用VirtualAlloc 在进程之间共享内存的内容发表评论。 VirtualAlloc 本身不允许与其他进程共享其保留/分配的内存。为此需要使用CreateFileMapping API,它可以创建一个可以与其他进程共享的命名虚拟内存块。它还可以将磁盘上的文件映射到虚拟内存中以进行读/写访问。但那是另一个话题了。
【讨论】:
VirtualAlloc 是 OS 虚拟内存 (VM) 系统的专用分配。 VM 系统中的分配必须以分配粒度进行,该分配粒度(分配粒度)取决于体系结构。 VM系统中的分配是内存分配的最基本形式之一。 VM 分配可以采用多种形式,内存不一定是专用的或物理支持在 RAM 中(尽管可以)。 VM 分配通常是一种特殊目的类型的分配,要么是因为分配必须
HeapAlloc 本质上是 malloc 和 new 最终调用的。它被设计为在通用分配的许多不同类型的场景下非常快速且可用。它是经典意义上的“堆”。堆实际上是由VirtualAlloc 设置的,它用于最初从操作系统保留分配空间。通过VirtualAlloc初始化空间后,配置各种表、列表等数据结构来维护和控制HEAP的运行。其中一些操作采用动态调整堆大小(增长和缩小)、使堆适应特定用途(某种大小的频繁分配)等形式。
new 和malloc 有点相同,malloc 本质上是对HeapAlloc( heap-id-default ) 的精确调用; new 但是,可以[另外] 为 C++ objects 配置分配的内存。对于给定的对象,C++ 将为每个调用者在堆上存储 vtable。这些 vtable 是用于执行的重定向,并且构成了 C++ 的 OO 特性(如继承、函数重载等)的一部分......
其他一些常见的分配方法,如_alloca() 和_malloca() 是基于堆栈的; FileMappings 实际上是用VirtualAlloc 分配的,并设置了特定的位标志,这些标志将这些映射指定为FILE 类型。
大多数时候,您应该以与该内存的使用一致的方式分配内存;)。 new 用于 C++,malloc 用于 C,VirtualAlloc 用于大型或 IPC 案例。
*** 请注意,HeapAlloc 完成的大内存分配实际上在经过一定大小(几百 k 或 16 MB 或其他我忘记的东西,但相当大 :) 后被运送到 VirtualAlloc。
*** 编辑
我简要介绍了 IPC 和 VirtualAlloc,还有一些关于相关的 VirtualAlloc 非常简洁的内容,对此问题的回复者都没有讨论过。
VirtualAllocEx 是一个进程可以用来在不同进程的地址空间中分配内存的方法。最典型的情况是,组合使用通过CreateRemoteThread在另一个进程的上下文中远程执行(类似于CreateThread,线程只是在另一个进程中运行)。
【讨论】:
大纲:
VirtualAlloc、HeapAlloc 等是直接从操作系统分配各种类型内存的 Windows API。 VirtualAlloc 管理 Windows 虚拟内存系统中的页面,而 HeapAlloc 从特定的 OS 堆分配。坦率地说,您不太可能需要使用其中任何一个。
malloc 是一个标准 C(和 C++)库函数,可为您的进程分配内存。 malloc 的实现通常会使用其中一个 OS API 在您的应用启动时创建一个内存池,然后在您发出 malloc 请求时从该池中分配
new 是一个标准 C++ 运算符,它分配内存,然后在该内存上适当地调用构造函数。它可以通过 malloc 或 OS API 来实现,在这种情况下,它通常也会在应用程序启动时创建一个内存池。
【讨论】:
VirtualAlloc ===> UNIX下sbrk()
HeapAlloc ====> UNIX下malloc()
【讨论】:
VirtualAlloc => 直接分配到虚拟内存,你保留/提交块。这对于大型分配非常有用,例如大型数组。
HeapAlloc / new => 在默认堆(或您可能创建的任何其他堆)上分配内存。这为每个对象分配,非常适合较小的对象。默认堆是可序列化的,因此它有保证的线程分配(这可能会在高性能场景中导致一些问题,这就是您可以创建自己的堆的原因)。
malloc => 使用 C 运行时堆,类似于HeapAlloc,但在兼容性场景中很常见。
简而言之,堆只是由堆管理器管理的一块虚拟内存(而不是原始虚拟内存)
内存世界的最后一个模型是内存映射文件,这种场景非常适合大块数据(如大文件)。这在您打开 EXE 时在内部使用(它不会将 EXE 加载到内存中,只是创建一个内存映射文件)。
【讨论】: