You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Windows平台实现跨平台内存泄漏检测工具?

实现跨平台内存泄漏检测工具:Windows平台的正确姿势

我正在开发一个跨平台的内存泄漏检测工具,核心逻辑很直接:

std::size_t before = MemoryAllocated();
do_stuff(); // 调用malloc、free、new、delete等内存操作的算法
std::size_t after = MemoryAllocated();
if( after != before ) printf( "ALARM! MEMORY LEAKS!\n" );

POSIX平台的实现

在POSIX系统上,这事很简单——用mallinfo()就能拿到全面的内存统计,其中的uordblks字段完全符合需求:它统计的是已分配的内存开销。比如在我的机器上,new int会分配32字节,当所有内存都释放后,这个字段的值会回到0,只要有泄漏就会是非0值,完美适配我的检测逻辑。

Windows平台的坑与解决方案

到了Windows平台,就得用_heapwalk()来遍历堆块计算已分配内存,但一开始的实现结果完全不符合预期:

测试代码:

int main() {
    std::cout << "start " << MemoryAllocated() << std::endl;
    char *charr = new char[100];
    std::cout << "after new char[100] " << MemoryAllocated() << std::endl;
    int *pint = new int;
    std::cout << "after new int " << MemoryAllocated() << std::endl;
    delete[] charr;
    std::cout << "after delete[] chars " << MemoryAllocated() << std::endl;
    delete pint;
    std::cout << "after delete int " << MemoryAllocated() << std::endl;
    return 0;
}

运行结果:

start 26980
after new char[100] 31176
after new int 31180
after delete[] chars 31080
after delete int 31076

从结果能看出来,首次申请内存时系统会为内部管理分配约4KB的初始内存,即使释放了所有自己申请的内存,统计值也回不到初始状态。我试过提前预分配内存来"预热"堆,但没起作用。那到底该怎么在Windows上正确实现这个功能?

正确的Windows平台实现思路

这里的核心问题是:Windows的堆管理器会保留一些内存用于自身的元数据和缓存,直接遍历所有堆块统计的话会把这些内部内存也算进去,导致我们的检测逻辑失效。我们需要区分应用程序主动分配的内存和堆管理器内部使用的内存。

方案1:Hook内存分配/释放函数

最可靠的方式是Hook住Windows的内存分配API(malloc、new、HeapAlloc等),自己维护一个已分配内存的计数器:

  • 每次调用分配函数时,记录分配的内存大小,加到计数器里
  • 每次调用释放函数时,减去对应内存的大小
  • 检测时直接对比计数器的前后值即可

这种方式完全不受堆管理器内部内存的影响,准确性最高。示例代码框架:

#include <unordered_map>

static size_t g_allocated_bytes = 0;
static std::unordered_map<void*, size_t> g_allocation_map;

// Hook malloc
void* my_malloc(size_t size) {
    void* ptr = malloc(size);
    if (ptr) {
        g_allocated_bytes += size;
        g_allocation_map[ptr] = size;
    }
    return ptr;
}

// Hook free
void my_free(void* ptr) {
    if (!ptr) return;
    auto iter = g_allocation_map.find(ptr);
    if (iter != g_allocation_map.end()) {
        g_allocated_bytes -= iter->second;
        g_allocation_map.erase(iter);
    }
    free(ptr);
}

// 重载operator new/delete
void* operator new(size_t size) {
    return my_malloc(size);
}

void operator delete(void* ptr) noexcept {
    my_free(ptr);
}

// 适配数组版new/delete
void* operator new[](size_t size) {
    return my_malloc(size);
}

void operator delete[](void* ptr) noexcept {
    my_free(ptr);
}

// MemoryAllocated函数直接返回计数器值
size_t MemoryAllocated() {
    return g_allocated_bytes;
}

方案2:过滤_heapwalk的堆块类型

如果不想Hook API,也可以优化_heapwalk的遍历逻辑:Windows的堆块有不同的类型,我们只统计应用程序分配的堆块(即_ENTRY_BUSY类型的块,且排除堆管理器的内部块)。

需要注意:堆块的cbData字段才是应用程序实际分配的大小,而cbSize是堆块的总大小(包含元数据)。另外,有些堆块是堆管理器自己用的,比如调试信息块,这些需要过滤掉。

优化后的MemoryAllocated实现示例:

#include <windows.h>
#include <malloc.h>

size_t MemoryAllocated() {
    size_t total = 0;
    _HEAPINFO heap_info{};
    int heap_walk_result;

    while ((heap_walk_result = _heapwalk(&heap_info)) == _HEAPOK) {
        // 只统计已分配的、非内部管理的堆块
        if (heap_info._useflag == _ENTRY_BUSY) {
            // 排除堆管理器的内部元数据块(比如调试堆的额外信息)
            if (heap_info._cbData > 0) {
                total += heap_info._cbData;
            }
        }
    }

    // 忽略遍历结束的正常结果(_HEAPEND)
    if (heap_walk_result != _HEAPEND) {
        // 处理错误,比如返回0或者记录日志
        return 0;
    }

    return total;
}

不过这种方式还是有局限性:比如某些调试堆的内部块可能无法完全过滤,而且不同Windows版本的堆结构可能有变化,兼容性不如Hook方案。

方案3:使用Windows的官方内存检测API

如果只是为了检测内存泄漏,也可以直接用Windows提供的_CrtMemCheckpoint系列函数(仅适用于调试版本):

#include <crtdbg.h>

void CheckForLeaks() {
    _CrtMemState stateBefore, stateAfter, stateDiff;
    _CrtMemCheckpoint(&stateBefore);

    do_stuff(); // 要检测的代码

    _CrtMemCheckpoint(&stateAfter);
    if (_CrtMemDifference(&stateDiff, &stateBefore, &stateAfter)) {
        _CrtMemDumpStatistics(&stateDiff);
        printf("ALARM! MEMORY LEAKS!\n");
    }
}

这种方式不需要自己实现统计逻辑,而且能给出详细的泄漏信息,但只能在调试模式下使用,无法用于发布版本的检测。

总结

如果需要跨平台且兼顾发布版本的检测,Hook内存分配函数是最可靠的方案;如果只是调试阶段用,Windows的_CrtMemCheckpoint系列函数更省心;而优化_heapwalk的遍历逻辑可以作为轻量级方案,但兼容性稍差。

内容的提问来源于stack exchange,提问作者Vasyl Demianov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:05:18