Visual Studio 包含本机内存探查器等各种分析和诊断工具。 此探查器与堆提供程序中的 ETW 事件挂钩,并分析如何分配和使用内存。 默认情况下,此工具仅可以分析从标准的 Windows 堆进行的分配,不会显示此本机堆以外的任何分配。
在很多情况下,你可能想要用自己的自定义堆,并避免标准堆的分配开销。 例如,可以使用 VirtualAlloc 在应用或游戏的开头分配大量内存,然后管理该列表中属于自己的块。 在此方案中,内存探查器工具只会看到初始分配,并且看不到在内存块内执行的自定义管理。 但是,通过使用自定义本机堆 ETW 提供程序,可让工具了解你在标准堆外进行的分配。
例如,在类似下面的项目中(其中 MemoryPool
为自定义堆),你将只能在 Windows 堆上看到一个分配:
class Foo
{
public:
int x, y;
};
...
// MemoryPool is a custom managed heap, which allocates 8192 bytes
// on the standard Windows Heap named "Windows NT"
MemoryPool<Foo, 8192> mPool;
// the "allocate" method requests memory from the pool created above
// and is cast to an object of type Foo, shown above
Foo* pFoo1 = (Foo*)mPool.allocate();
Foo* pFoo2 = (Foo*)mPool.allocate();
Foo* pFoo3 = (Foo*)mPool.allocate();
在不含自定义堆跟踪的内存使用情况工具的快照中,仅显示一个 8192 字节分配,池未进行任何自定义分配:
通过执行以下步骤,我们可以使用同一工具跟踪自定义堆中的内存使用情况。
如何使用
可以在 C 和 C++ 中轻松使用此库。
包括自定义堆 ETW 提供程序的标头:
#include <VSCustomNativeHeapEtwProvider.h>
将
__declspec(allocator)
装饰器添加到自定义堆管理器的任何函数中,该函数向新分配的堆内存返回指针。 使用此装饰器可正确标识将返回的内存类型。 例如:__declspec(allocator) void *MyMalloc(size_t size);
注意
此修饰器将告知编译器该函数是对某分配器的调用。 每次调用函数都将输出调用点的地址、调用指令的大小和新的
S_HEAPALLOCSITE
符号的新对象的 typeId。 分配调用堆栈后,Windows 会发出具有此信息的 ETW 事件。 内存探查器工具浏览调用堆栈以查找匹配S_HEAPALLOCSITE
符号的返回地址,并且符号中的 typeId 信息用于显示分配的运行时类型。简言之,这表示类似
(B*)(A*)MyMalloc(sizeof(B))
的调用将显示在该工具中,且类型为B
,而不是void
,也不是A
。对于 C++,创建
VSHeapTracker::CHeapTracker
对象,提供堆名称,这将显示在分析工具中:auto pHeapTracker = std::make_unique<VSHeapTracker::CHeapTracker>("MyCustomHeap");
如果使用 C,请改用
OpenHeapTracker
函数。 调用其他跟踪函数时,此函数将返回你将使用的句柄:VSHeapTrackerHandle hHeapTracker = OpenHeapTracker("MyHeap");
使用自定义函数分配内存时,调用
AllocateEvent
(C++) 或VSHeapTrackerAllocateEvent
(C) 方法,传入指向内存及其大小的指针,以跟踪分配:pHeapTracker->AllocateEvent(memPtr, size);
或
VSHeapTrackerAllocateEvent(hHeapTracker, memPtr, size);
重要
别忘了使用前面所述的
__declspec(allocator)
修饰器标记自定义分配器函数。使用自定义函数释放内存时,调用
DeallocateEvent
(C++) 或VSHeapTracerDeallocateEvent
(C) 函数,传入指向内存的指针,以跟踪释放:pHeapTracker->DeallocateEvent(memPtr);
或者:
VSHeapTrackerDeallocateEvent(hHeapTracker, memPtr);
使用自定义函数重新分配内存时,调用
ReallocateEvent
(C++) 或VSHeapReallocateEvent
(C) 方法,传入指向新内存、分配大小的指针以及指向旧内存的指针:pHeapTracker->ReallocateEvent(memPtrNew, size, memPtrOld);
或者:
VSHeapTrackerReallocateEvent(hHeapTracker, memPtrNew, size, memPtrOld);
最后,若要关闭并清除 C++ 中的自定义堆跟踪器,请通过手动或通过标准作用域规则或 C 中的
CloseHeapTracker
函数使用CHeapTracker
析构函数:delete pHeapTracker;
或者:
CloseHeapTracker(hHeapTracker);
跟踪内存使用情况
随着这些调用就位,现可使用 Visual Studio 中的标准内存使用量工具跟踪自定义堆使用情况。 有关如何使用此工具的详细信息,请参阅内存使用量文档。 确保已通过快照启用堆分析,否则你的自定义堆使用情况将不会显示。
若要查看自定义堆跟踪,请使用位于“快照”窗口右上角的“堆”下拉菜单,将视图从“NT 堆” 更改为之前已命名的自己的堆。
通过上面的代码示例,当 MemoryPool
创建 VSHeapTracker::CHeapTracker
对象,并且我们自己的 allocate
方法在调用 AllocateEvent
方法时,你可以查看该自定义分配的结果,结果显示三个实例,合计 24 个字节,均为类型 Foo
。
默认的 NT 堆看起来与前面的相同,同时添加了 CHeapTracker
对象。
如文档中所述,与标准的 Windows 堆一样,你还可使用此工具比较快照,并查找自定义堆中的泄漏和损坏。
提示
Visual Studio 在性能分析工具集中还包含内存使用情况工具,可在“调试”>“性能探查器”菜单选项或通过 Alt+F2 组合键启用该工具。 此功能不包含堆跟踪,并且不会按如下所述显示你的自定义堆。 只有“诊断工具”窗口包含此功能,可通过选择“调试”>“Windows”>“显示诊断工具”菜单或 Ctrl+Alt+F2 组合键来启用此窗口。