Visual Leak Detector 报告一个 int* 泄露了40个字节

7

这是我的程序

#include <vld.h>

using namespace std;

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

Visual Leak Detector 报告

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字节 的内存泄漏,实际上应该只有 4字节

有人能解释一下这是怎么回事吗?


2
分配的内存块总是比最初请求的要大。 - πάντα ῥεῖ
2
@Robinson int* 表示的是 int(100) 而不是 int[100]。 - user1
1
考虑 X* p = new X[y],当你调用 delete[] p 时,所有 y 个析构函数都会被调用。然而,delete 只得到一个单一的指针。它怎么知道数组的长度呢?因为它存储在分配的块中,这个块比分配对象所需的内存要大得多。 - xryl669
2个回答

5
首先,当您请求4字节的分配时,很有可能会得到一个更大的块(这是安全的,因为您只应该使用您所请求的4个字节)。
为什么呢?
  1. 分配大小必须存储在某个地方(考虑new X[count]的情况,以及必须调用count次X的析构函数的delete[])。
  2. 然后,堆分配通常是通过堆的递归碎片化来完成的,例如Buddy_memory_allocation。这是因为你希望尽可能低的开销(即用于管理分配的字节数与实际分配的字节数相比)。你需要记住某些内存块是否被使用。
  3. 调试也可能添加分配大小。在Visual Studio中,malloc/free函数在返回指针之前插入了4个字节的“内存保护”,例如(0xDDDDDDDD),并为另一个内存保护分配了4个字节。当您调用malloc或free(间接地,使用new和delete)时,堆处理程序的代码检查保护并断言它们没有被修改。如果它们被修改,它就停止你的程序,这样你就可以看到修改内存的地方。如果我没记错,0xCDCDCDCD用于填充分配区域,0xFEEEFEEE用于填充已释放的区域,但块尚未返回给系统,而0xDDDDDDDD用于边界。
因此,即使您只使用“4”,您收到的块的大小似乎是40字节(可能更多)。VLD不跟踪您的代码,它截取内存管理函数(如malloc/free),并构建每个分配块的列表。当它们被释放时,这个列表会被解析以删除元素。在终止时,任何剩余的项目都会被列出。
因此,malloc调用很可能来自于一个::operator new,它将请求的大小扩大到40个字节,或者32个字节的块被添加到VLD中以跟踪“分配请求”。
在查看VLD源代码之后,特别是vldnew函数,它为每个分配分配一个头:
vldblockheader_t *header = (vldblockheader_t*)RtlAllocateHeap(g_vldHeap, 0x0, size + sizeof(vldblockheader_t))

很可能,在您的情况下,vldblockheader_t 的大小为36个字节。

即使考虑调试开销、守卫字节、存储分配大小,40个字节对于一个字节的考虑来说太大了。10倍的开销实在是太多了! - user1
1
我认为如果在Windows上有一个类似于strace的工具,实际请求的块可能是64个字节或更多,而不是40个字节。然而,如果你再分配一个int,返回的指针可能仍然在同样大小为2^n的内存块中,因此从操作系统的角度来看,它不会额外消耗堆内存。内存分配算法通常比较摊销时间,而不是单个分配。 - xryl669
另一个需要注意的问题是,堆上分配内存比栈上分配内存更加昂贵。因此建议尽可能使用栈分配内存。 - xryl669
我添加了两个新语句,int* p1 = new int(100); int* p2 = new int(100);,现在总共泄漏的内存是120字节。实际上,这是线性增长。40字节 * n次分配。我的说法仍然成立——对于一个整数来说,_40字节_太多了。你在你的回答中漏掉了一些重要的细节。 - user1
2
好的,我已经搜索了VLD源代码,确实存在VLD开销。请查看我的编辑。 - xryl669

0
为什么会有40字节的内存泄漏,实际上应该只有4字节。
这与动态分配对象的附加信息以及动态(堆)内存的高效管理有关。
关于前者,应该有可用的信息,以便在对象生命周期结束后释放分配的堆内存。
至于后者,有一个叫做“容量”的东西,它不一定等于分配的大小。它可以相等或更大,额外的空间可以容纳增长而无需在每次插入时重新分配。
请注意,这个容量并不对类型的大小施加限制,在您的情况下是int。
例如:
向量是表示可以改变大小的数组的序列容器。在内部,向量使用动态分配的数组来存储它们的元素。 调用以下三个向量成员函数:size()、max_size()和capacity()。

将返回不同的值,并为您提供在分配堆内存时使用的策略的一些见解。


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

2. 可以添加额外的填充以进行内存对齐(4字节的倍数),以便可以使用更少的内存访问来读取。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接