【问题标题】:Visual Leak Detector reports 40 bytes leaked for one int*Visual Leak Detector 报告 40 个字节泄漏 1 个 int*
【发布时间】:2015-10-01 10:41:22
【问题描述】:

这是我的程序

#include <vld.h>

using namespace std;

int main() {
    int* p = new int(100);
}

视觉检漏报告

Visual Leak Detector Version 2.3 installed.
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 1 at 0x00891B60: 4 bytes ----------
  Call Stack:
    c:\xxx\documents\visual studio 2010\projects\stl1\stl1\stl1.cpp (11): stl1.exe!main + 0x7 bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (555): stl1.exe!__tmainCRTStartup + 0x19 bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (371): stl1.exe!mainCRTStartup
    0x76B7338A (File and line number not available): kernel32.dll!BaseThreadInitThunk + 0x12 bytes
    0x774B97F2 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x63 bytes
    0x774B97C5 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x36 bytes
  Data:
    64 00 00 00                                                  d....... ........


Visual Leak Detector detected 1 memory leak (40 bytes).
Largest number used: 40 bytes.
Total allocations: 40 bytes.
Visual Leak Detector is now exiting.
The program '[8992] stl1.exe: Native' has exited with code 0 (0x0).

为什么是40 bytes 内存泄漏,真的应该是4 bytes

谁能解释这里发生了什么?

【问题讨论】:

  • 分配的内存块总是比最初请求的要大。
  • @Robinson int* 是 int(100);而不是 int[100]。
  • 想想X* p = new X[y],当你调用delete[] p时,所有的y析构函数都会被调用。但是,delete 只得到一个指针。它如何知道数组有多长?因为它存储在分配的块中,远远大于分配对象所需的内存。

标签: c++ memory-leaks visual-leak-detector


【解决方案1】:

首先,当您要求分配 4 个字节时,您总是会得到一个更大的块的可能性很高(这是安全的,因为您应该只使用您要求的 4 个字节)。

为什么?

  1. 分配大小必须存储在某个地方(想想new X[count] 的情况,delete[] 必须调用 count 乘以 X 的析构函数

  2. 然后,堆分配通常通过堆的递归分段来完成,例如the Buddy_memory_allocation。 这是因为您希望开销尽可能低(即用于管理分配的字节数与实际分配的字节数相比)。您需要记住是否使用了某些内存块。

  3. 调试也可能会增加分配大小。在 Visual Studio 上,malloc / free 函数在返回的指针之前插入 4 个字节,并带有一个像 (0xDDDDDDDD) 这样的“内存保护”,并在请求的大小之后为另一个内存保护分配 4 个字节。当您调用 malloc 或 free(间接地,new 和 delete)时,堆处理程序的代码会检查守卫并断言它们没有被修改。如果是,它会停止你的程序,这样你就可以看到修改内存的“周围”。 IIRC,0xCDCDCDCD 用于填充分配区域,0xFEEEFEEE 用于填充已释放区域但块尚未返回系统,0xDDDDDDDD 用于边界。

因此,即使您只使用“4”,您收到的块的大小似乎也是 40 字节(可能更多)。 VLD 不会跟踪您的代码,它会拦截内存管理函数(如malloc/free),并构建每个已分配块的列表。此列表被解析以在元素被释放时移除元素。终止后,将列出任何剩余项目。

所以收到的 malloc 调用可能来自 ::operator new,它将请求的大小扩大到 40 字节,或者可能在 VLD 中添加了一个 32 字节的块来跟踪“分配请求”。

看了VLD source code之后,特别是vldnew函数,它为每个分配分配一个头:

vldblockheader_t *header = (vldblockheader_t*)RtlAllocateHeap(g_vldHeap, 0x0, size + sizeof(vldblockheader_t))

在您的情况下,vldblockheader_t 可能是 36 个字节。

【讨论】:

  • 即使考虑调试开销、保护字节、存储分配大小;考虑到一个字节,40 字节要大得多。 10 倍的开销实在是太多了!
  • 我认为,如果您在 Windows 上具有类似 strace 的等价物,则请求的实际块可能是 64 字节或更多而不是 40。但是,如果您分配另一个 int,则返回的指针可能是在同一个 2^n 大小的内存块中,因此从操作系统的角度来看,它将花费 0 个额外的堆。内存分配算法通常在摊销时间内进行比较,而不是针对单个分配。
  • 另一个注意事项,但更普遍的是,在堆上分配比在堆栈上分配更昂贵。这就是为什么建议尽可能使用堆栈分配。
  • 我添加了 2 个新语句 int* p1 = new int(100); int* p2 = new int(100); 现在,泄漏的总内存为 120 字节。它实际上是线性增长。 40 bytes * n allocations。我的说法仍然成立 - 40 bytes for one int* 实在是太多了。您在回答中遗漏了一些主要细节。
  • 好的,我已经搜索了 VLD 源代码,这确实是 VLD 开销。请看我的编辑
【解决方案2】:

为什么40字节内存泄漏,真的应该是4字节。

它与动态分配对象的附加信息和动态(堆)内存的高效1、2管理有关。

关于前者,应该有可用的信息,以便在对象生命周期结束后释放分配的堆内存。

对于后者,有一种叫做容量的东西,它不一定等于分配的大小。它可以等于或更大,额外的空间可以适应增长,而无需在每次插入时重新分配。

请注意,此容量不假设类型大小的限制,在您的情况下为 int

示例:

vectors 是序列容器,表示可以改变大小的数组。在内部,向量使用动态分配的数组来存储它们的元素。 调用以下三个vector 成员函数:

  • size()

  • max_size()

  • capacity()

将返回不同的值,并让您了解分配堆内存时使用的策略。


1.如果最初分配的对象需要增长,则可能需要完全重新分配,而不是扩展到相邻/连续的内存部分。涉及很多操作。

2。可以为内存对齐添加额外的填充(4 字节的倍数,以便可以通过更少的内存访问来读取)

【讨论】: