Alloc API可以在内部调用VirtualAlloc / reserve内存吗?

时间:2016-11-30 05:51:31

标签: c++ windows winapi dll virtual-memory

我正在调试调试DLL中潜在的内存泄漏问题。

案例是该进程运行一个子测试,动态加载/卸载DLL,在测试期间保留并提交了大量内存(1.3GB)。测试完成并卸载DLL后,仍保留大量内存(1.2GB)。

我说这个保留内存由DLL分配的原因是,如果我使用发布DLL(没有其他改变,同样的测试),保留内存大约是300MB,所以所有额外的保留内存必须在调试DLL中分配。

看起来在测试期间会提交大量内存但在测试后会解除(不释放到空闲状态)。所以我想跟踪谁保留/解除那个大内存。但是在源代码中,没有调用VirtualAlloc,所以问题是:

  1. VirtualAlloc是保留内存的唯一方法吗?
  2. 如果没有,其他API可以做什么?如果是,其他API将在内部调用VirtualAlloc?有网上有人说HeapAlloc会在内部调用VirtualAlloc吗?它是如何运作的?

1 个答案:

答案 0 :(得分:4)

[部分内容纯粹是实现细节,而不是您的应用程序应该依赖的内容,因此仅将其用于提供信息,而不是官方文档或任何类型的合同。也就是说,如果仅用于调试目的,了解事情如何在幕后实现是有价值的。]

是的,VirtualAlloc()函数是Windows中内存分配的主力函数。它是一个低级功能,如果您需要其功能,操作系统可以使用它,也可以是系统内部使用的功能。 (确切地说,它可能没有直接调用VirtualAlloc(),而是VirtualAlloc()也调用的更低级别的函数,比如NtAllocateVirtualMemory(),但这只是语义而且没有&# 39;改变可观察的行为。)

因此,HeapAlloc()构建在VirtualAlloc()之上,GlobalAlloc()和LocalAlloc()也是如此(尽管后两者在32位Windows中已经过时,应该基本上不会被应用程序使用 - 更喜欢显式调用HeapAlloc())。

当然,HeapAlloc()不仅仅是VirtualAlloc()的简单包装器。它增加了一些自己的逻辑。 VirtualAlloc()总是以大块分配内存,由系统的分配粒度定义,这是特定于硬件的(可通过调用GetSystemInfo()和读取SYSTEM_INFO.dwAllocationGranularity的值来检索)。 HeapAlloc()允许您以所需的任何粒度分配较小的内存块,这更适合于典型的应用程序编程。在内部,HeapAlloc()处理调用VirtualAlloc()以获取大块,然后根据需要对其进行处理。这不仅提供了更简单的API,而且效率更高。

请注意,C运行时库(CRT)提供的内存分配函数 - 即C ++的malloc()和C ++的新运算符 - 是更高级别的。它们建立在HeapAlloc()之上(至少在Microsoft的CRT实现中)。在内部,他们分配了相当大的内存,基本上用作" master"应用程序的内存块,然后根据请求将其分成更小的块。当您释放/删除这些单独的块时,它们将返回到池中。再一次,这个额外的层提供了一个简化的接口(特别是编写与平台无关的代码的能力),以及在一般情况下提高效率。

各种OS API提供的内存映射文件和其他功能也建立在虚拟内存子系统上,因此内部调用VirtualAlloc()(或更低级别的等价物)。

所以是的,从根本上说,普通Windows应用程序的最低级别内存分配例程是VirtualAlloc()。但这并不意味着它通常用于内存分配的主力函数。如果您确实需要其附加功能,请仅调用VirtualAlloc()。否则,要么使用标准库的内存分配例程,要么有一些令人信服的理由要避免它们(比如没有链接到CRT或创建自己的自定义内存池),请调用HeapAlloc()。

另请注意,必须始终使用与用于分配内存的机制相对应的机制释放/释放内存。仅仅因为所有内存分配函数最终调用VirtualAlloc()而不是意味着您可以通过调用VirtualFree()来释放该内存。如上所述,这些其他函数在VirtualAlloc()之上实现了额外的逻辑,因此需要您调用自己的例程来释放内存。如果您通过调用VirtualAlloc()自行分配内存,则只调用VirtualFree()。如果内存是使用HeapAlloc()分配的,则调用HeapFree()。对于malloc(),调用free();对于新的,呼叫删除。

至于你问题中描述的具体情况,我不清楚你为什么担心这个问题。重要的是要记住保留内存和已提交内存之间的区别。保留仅表示地址空间中的此特定块已保留供进程使用。不能使用保留块。为了使用内存块,必须提交它,这是指在页面文件或物理内存中为内存分配后备存储的过程。这有时也称为映射。保留和提交可以作为两个单独的步骤完成,也可以同时完成。例如,您可能希望保留一个连续的地址空间以供将来使用,但您实际上并不需要它,因此您不会提交它。已保留但未提交的内存实际上未分配。

事实上,所有这些保留的内存可能都不是泄漏。在调试中使用的一个相当常见的策略是保留特定范围的内存地址,而不提交它们,以阻止尝试访问此范围内的内存,并发现"访问冲突"例外。在发布模式下编译时,您的DLL未进行大量预留这一事实表明,这可能是一种调试策略。它还提出了一种更好的确定源的方法:不是扫描代码查找所有内存分配例程,而是扫描代码以查找依赖于构建配置的条件代码。如果您在定义DEBUG_DEBUG时做了不同的事情,那么这可能就是魔术发生的地方。

另一种可能的解释是CRT对malloc()或new的实现。当你分配一小块内存(比如几KB)时,CRT实际上会保留一个更大的块但只提交一个请求大小的块。当您随后释放/删除该小块内存时,它将被解除授权,但较大的块将不会释放回操作系统。这样做的原因是允许将来调用malloc / new来重用该保留的内存块。如果后续请求用于比当前保留的地址空间可满足的更大的块,则它将保留额外的地址空间。如果在调试版本中,您反复分配和释放越来越大的内存块,那么您所看到的内容可能是内存碎片的结果。但这不是一个问题,除了轻微的性能损失,在调试版本中真的不值得担心。