zoukankan      html  css  js  c++  java
  • 技术回归01-Windows内存分配工具

    很久没有写技术方面的东西了,这半年主要是在学习别人的东西,对自己提高比较大,算是一次技术回笼吧,这次学习之旅目的是结束技术方面的专注,开始向应用方面找突破口,也就是完成技术积累或者为技术的积累做坚实的准备。

    c/C++的一个让人疯狂的地方就是内存管理,非法访问、越界、野指针、泄漏、内存分配器等诸多问题,有时候一个编程老手也会迷惘困惑。Crt有一些堆栈检查的函数可以完成基本的内存状况检查,MFC也有一些简单的对象检查机制,当然好的算是java、.net等sdk的超重量级封装了,即使发生对象错误也能把堆栈信息明明白白的告诉你(至少表面上是这样,具体我对这两种语言没有做过开发)。下面介绍的是某牛公司实现的内存分配工具,基本实现了内存泄漏检查,对象合法性检查,对于我来说已经够用了。


    为了对内存分配块进行跟踪,设计如下结构体:

    //+--------------------------------------------------------------
    //
    // 每个请求分配内存块的前缀结构体
    // 用来跟踪所有请求分配块以及请求分配名称
    //
    //---------------------------------------------------------------
    struct DBGALLOCHDR
    {
        DBGALLOCHDR*    pdbgahPrev; // 前一个内存块头
        DBGALLOCHDR*    pdbgahNext; // 后一个内存块头
        DWORD           iAllocated; // 记录是第几次请求分配操作
        DWORD           tid;        // 请求分配线程的ID
        size_t          cbRequest;  // 请求分配大小
        char            szName[64]; // 请求分配块名称
        DWORD           adwGuard[4];// 保护头
    };

    //+--------------------------------------------------------------
    //
    // 每个请求分配内存块的后缀结构体
    // 使用特定的数据填充用来检测指针是合法
    //
    //---------------------------------------------------------------
    struct DBGALLOCFOOT
    {
        DWORD adwGuard[4];
    };

    // 内存跟踪块的根,通过根可以获取所有分配块
    DBGALLOCHDR g_dbgahRoot =
    {
        &g_dbgahRoot,
        &g_dbgahRoot,
        0,
        (DWORD)-1
    };



    为了实现多线程内存分配跟踪,采用Tls技术使用线程局部对象保存当前分配信息:

    // 线程局部对象结构体,辅助实现每个线程的请求内存分配记录
    struct DBGTHREADSTATE
    {
        DBGTHREADSTATE* ptsNext;
        DBGTHREADSTATE* ptsPrev;

        // Add globals below
        void*           pvRequest;  // 线程最后一次请求分配内存的指针
        size_t          cbRequest;  // 线程最后一次请求分配内存的大小
    };



    // 调试期间实际分配内存大小=请求分配+分配头+分配尾
    size_t _ActualSizeFromRequestSize(size_t cb)
    {
        return cb+sizeof(DBGALLOCHDR)+sizeof(DBGALLOCFOOT);
    }



    主要实现的内存分配工具有如下这些:

    void*   _MemAlloc(ULONG cb);
    void*   _MemAllocClear(ULONG cb);
    HRESULT _MemRealloc(void** ppv, ULONG cb);
    ULONG   _MemGetSize(void* pv);
    void    _MemFree(void* pv);
    HRESULT _MemAllocString(LPCTSTR pchSrc, LPTSTR* ppchDst);
    HRESULT _MemAllocString(ULONG cch, LPCTSTR pchSrc, LPTSTR* ppchDst);
    HRESULT _MemReplaceString(LPCTSTR pchSrc, LPTSTR* ppchDest);

    #define MemAlloc(cb)                            _MemAlloc(cb)
    #define MemAllocClear(cb)                       _MemAllocClear(cb)
    #define MemRealloc(ppv, cb)                     _MemRealloc(ppv, cb)
    #define MemGetSize(pv)                          _MemGetSize(pv)
    #define MemFree(pv)                            _MemFree(pv)
    #define MemAllocString(pch, ppch)               _MemAllocString(pch, ppch)
    #define MemAllocStringBuffer(cch, pch, ppch)    _MemAllocString(cch, pch, ppch)
    #define MemReplaceString(pch, ppch)             _MemReplaceString(pch, ppch)
    #define MemFreeString(pch)                      _MemFree(pch)



    通过宏实现类的new delete重写:

    #define DECLARE_MEMALLOC_NEW_DELETE() 
        inline void* __cdecl operator new(size_t cb)    { return(MemAlloc(cb)); } 
        inline void* __cdecl operator new[](size_t cb)  { return(MemAlloc(cb)); } 
        inline void __cdecl operator delete(void* pv)   { MemFree(pv); }

    #define DECLARE_MEMCLEAR_NEW_DELETE() 
        inline void* __cdecl operator new(size_t cb)    { return(MemAllocClear(cb)); } 
        inline void* __cdecl operator new[](size_t cb)  { return(MemAllocClear(cb)); } 
        inline void __cdecl operator delete(void* pv)   { MemFree(pv); }



    在应用的时候可以重写全局new delete:

    // 测试全局new delete
    void* __cdecl operator new(size_t cb)    { return(MemAlloc(cb)); }
    void* __cdecl operator new[](size_t cb)  { return(MemAlloc(cb)); }
    void __cdecl operator delete(void* pv)   { MemFree(pv); }


    使用注意:
    进程启动时候需要调用:
    _DbgDllProcessAttach();
    _afxGlobalData._hProcessHeap = GetProcessHeap();

    进程退出的时候需要调用:
    _DbgDllProcessDetach();

    测试用例:

    // 测试基本类型
    void TestBuiltin()
    {
        // 基本类型
        int* pInt = new int(10);
        int* pIntAry = new int[10];
        char* pStr = new char[100];
        MemSetName((pStr, "String"));
    }

    // 测试class
    void TestClass()
    {
        Cls* pCls = new Cls();
    }

    // 测试释放
    void TestOk()
    {
        Cls* pCls = new Cls();
        delete pCls;
        pCls = NULL;
    }

    DWORD WINAPI ThreadProc(LPVOID lpParameter)
    {
        int* pIntAry = new int[100];
        return 0;
    }

    // 测试多线程
    void TestMultiThread()
    {
        HANDLE hHandle = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        WaitForSingleObject(hHandle, -1);
    }

    int main(int argc, char* argv[])
    {
        _DbgDllProcessAttach();

        _afxGlobalData._hProcessHeap = GetProcessHeap();

        TestBuiltin();
        TestClass();
        TestMultiThread();
        TestOk();

        _DbgDllProcessDetach();
        return 0;
    }


    调试输出窗口结果:
    A +    4 -    0 = [      4]
    A +   40 -    0 = [     44]
    A +  100 -    0 = [    144]
    A +    8 -    0 = [    152]
    A +  400 -    0 = [    552]
    The thread 0x1D38 has exited with code 0 (0x0).
    A +    8 -    0 = [    560]
    F +    0 -    8 = [    552]
    ---------- Leaked Memory Blocks ----------
    p=0x00144354  cb=400  #=4    TID:0x1d38 
    p=0x00144294  cb=8    #=3    TID:0x1878 
    p=0x001441a4  cb=100  #=2    TID:0x1878 String
    p=0x001440ec  cb=40   #=1    TID:0x1878 
    p=0x00142a54  cb=4    #=0    TID:0x1878 
    total size 552, peak size 560
    ---------- Leaked Memory Blocks End ------


    其中A表示分配 F表示释放

    该工具本人初试没有中毒症状,打算纳入个人小宝库中,希望大家喜欢!

    下载

    http://www.cppblog.com/wlwlxj/archive/2009/06/03/86660.html

  • 相关阅读:
    bzoj2298 [HAOI2011]problem a
    P5504 [JSOI2011]柠檬
    洛谷P4383 [八省联考2018]林克卡特树
    [USACO17DEC]Standing Out from the Herd
    bzoj3926: [Zjoi2015]诸神眷顾的幻想乡
    dtoj4680. 红黑兔
    dtoj2099. 字符串查询( find)
    dtoj1721. 字符串生成器 ( strgen )
    dtoj4542. 「TJOI / HEOI2016」字符串
    loj2278. 「HAOI2017」字符串
  • 原文地址:https://www.cnblogs.com/findumars/p/5403036.html
Copyright © 2011-2022 走看看