当前位置:首页 > 公众号精选 > CPP开发者
[导读]之前已经写过四篇关于Windows中如何查找内存泄露的方法,基本上可以帮你找到内存泄露的问题所在。查看系列文章请发送关键字 内存泄漏 获取。那么为什么要写这篇文章呢?本人在逛知乎的时候,看到一个问题,不乏很多高手的回答。我正好也写了几篇通过工具去分析内存泄露的文章,那先说说工具的...

之前已经写过四篇关于Windows中如何查找内存泄露的方法,基本上可以帮你找到内存泄露的问题所在。查看系列文章请发送关键字 内存泄漏 获取。

那么为什么要写这篇文章呢?

本人在逛知乎的时候,看到一个问题<>, 不乏很多高手的回答。我正好也写了几篇通过工具去分析内存泄露的文章,那先说说工具的方法原理:

  1. 对内存的分配的监测: 记录内存申请时候函数调用栈。一种方法是通过gflag配置让程序在分配内存的时候,记录函数调用栈;还有一种就是通过hook的方式去获取申请内存时候函数调用时候的位置。
  2. 对比程序运行时两个不同时间点的内存分配状况,通过对比找到较多的内存分配点对应的函数调用栈
那么回到正题,如果自己去实现无非就是要实现以上两点。本人正好在上学的时候用过微软 DEBUG CRT库检测过内存泄露,那就让我们一起再来看看其原理,也正是可以自己去实现的一种方法,要做到知其然知其所以然

微软Debug CRT库检测C 内存泄露原理?

我们先来解决上述的两个问题。

问题一: 如何获取函数调用栈?

那么你首先要知道什么时候申请了内存?在C 中也就关键字new或者函数malloc,等等。那如何感知到呢?我们知道hook大致可以理解为就是改变原有的函数调用地址,改为你自己实现的函数。是不是有点类似于python中的装饰器了,在自定义的函数内部实现一些逻辑。不过本文要讲的不是hook,而是宏替换。以malloc为例,我们是不是可以通过宏定义,将malloc更改为my_malloc,然后在my_malloc中记录这次内存申请的信息。然后记录的信息要包括:

  1. 申请的内存信息,比如申请的内存状态
  2. 申请内存时候函数调用栈,一般来说可以通过StackWalk获取。不过本文讲解的微软DBUG的CRT库采用的是另外的方式,记录内存申请时候文件名和行号等信息。这样虽然没有函数调用栈精确,但是也基本可以用于定位问题了。
在Visual Studio (本文示例采用Visual Studio 2017)中,选择工程的默认的Debug模式,并且工程配置宏定义_CRTDBG_MAP_ALLOC, 此时将宏定义替换malloc_malloc_dbg。注意看新的函数会传入文件名字__FILE__和所在行__LINE__

#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
那么malloc做的事情和_malloc_dbg有什么区别呢? 在Release版本中malloc底层其实就直接调用HeapAlloc申请内存(VS2017中)。而_malloc_dbg会申请额外的空间用来做调试用。如下图所示: 在_malloc_dbg中在实际要用的内存UserPtr前面还加了一段_CrtMemBlockHeader用于记录内存申请的相关信息,而No Main's Land部分为一个4个字节填充了0xFDFDFDFD,主要用来校验内存是否溢出或者破坏,这个不是本文的重点。接下来看看_CrtMemBlockHeader是如何记录调用相关的信息的呢? 我们看下它的结构便一目了然。其是一个双向链表节点,有前后指针,还有文件名,行号等。

struct _CrtMemBlockHeader
{
    _CrtMemBlockHeader* _block_header_next;
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;

    int                 _block_use;
    size_t              _data_size;

    long                _request_number;
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
};
那么当申请了内存后,这些内存的关系是如何的呢,如下图:

那通过以上方法我们便可以对每一个内存申请做记录了,而这个记录则存储在全局的链表中__acrt_first_block

那么内存释放的时候,是如何进行释放的呢?同样的free也会通过宏替换为_free_dbg,这里在进行内存释放的时候,会根据UserPtr寻找到对应的_CrtMemBlockHeader, 也就知道了链表节点的位置,双向链表,也便于我们删除节点。

看到这里可能有同学会发现了,那还有C 的关键字newdelete呢。首先我们要知道new是C 的关键字,对于有构造函数的类一般做了以下两个事情:

  1. 申请对象所需的内存空间。而这个时候内部其实调用的是函数operator new或者operator new[]
  2. 调用对象的构造函数
而在微软crt中也有对new记录文件名和行号的实现。

    void* __CRTDECL operator new(
        size_t const size,
        int const    block_use,
        char const*  file_name,
        int const    line_number
        )
本人没有找到哪个头文件直接定义了宏替换,那么我们可以自己写一个宏进行替换如下:

#define new new(_NORMAL_BLOCK, __FILE__, __LINE__) 
那么不难理解其他的内存操作函数如何去做替换了吧。

问题二: 对比不同时间点的内存分配情况

那么我们如何去对比呢?我先写了一个样例程序:

#define _CRTDBG_MAP_ALLOC
#include 
#include 
#define new   new(_NORMAL_BLOCK, __FILE__, __LINE__) 
int main()
{
 //_CRTDBG_REPORT_FLAG:表示获取当前的标示位
    //_CRTDBG_LEAK_CHECK_DF:表示检测内存泄露
 _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
 int iSize = 100;
 char * pStr = new char [iSize];
 pStr = (char*)malloc(iSize);
 strcpy_s(pStr, iSize, "Memory Leak!");
 _CrtDumpMemoryLeaks();
 return 0;
}
因为这个是一个简单的样例程序,但是足以说明是如何检测的。

  1. 一种方式是自己在程序中主动打印出来可能泄露的内存。这个时候其实就是遍历上述的双向链表,查看正在使用的内存,并将其打印到Visual Studio的output窗口中。
  2. 另一种方式就是设置_CRTDBG_LEAK_CHECK_DF这个标记位,则在main函数退出后,在Debug的CRT库中主动调用了_CrtDumpMemoryLeaks。其实和方法1原理一样,只是时间点不同。
检测到的结果打印在Visual Studio的Output窗口中,如下图所示。

总结

简单总结下,微软Debug CRT库的实现,完全可以在项目中自己实现。就是通过在申请的内存头部记录当前分配内存的相关信息,比如文件名行号,并且通过双向链表将所有申请的节点串起来。然后在合适的时间点(比如感知到内存泄露的情况下)打印出可能的内存泄露的内存关联的信息。这种做法简单,但只针对小型的项目,适合采用这种方法,而且对于第三方库的内存泄露无法进行检测。本文旨在通过分析微软Debug CRT库的实现的检测内存泄露的方式,从而阐述自我实现简易C 内存泄露检测的思想。若平时分析内存泄露问题,建议还是采用本文开头提到的几篇文章的方法。

参考

  • Walking the callstackhttps://www.codeproject.com/Articles/11132/Walking-the-callstack-2
  • C 不用工具,如何检测内存泄漏?https://www.zhihu.com/question/29859828
  • new vs operator new in C :*https://www.geeksforgeeks.org/new-vs-operator-new-in-cpp/


- EOF -

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

「人工智能浪潮下的中国制造」论坛顺利召开 上海2024年4月17日 /美通社/ -- 4月12日,由百年名校法国里昂商学院主办,斯巴诺萨设计承办,福州东湖数字小镇,福建亚太合会数字经济专委会协办的"中法建交6...

关键字: 微软 雷诺 AI 中国制造业

4月21日消息,Meta发布超级彪悍的大语言模型Llama 3之后,微软也很快推出了自己的新一代WizardLM2 8x22B,号称迄今最强大,完全超越Claude 3 Opus&Sonnet、GPT-4等竞品,而且开源...

关键字: 微软 AI 机器AI 比尔盖茨

近日媒体Business Insider透露称,微软目前正在疯狂囤货GPU,目标在2024年12月前达到180万片。微软本次采购的 GPU 主要来自英伟达公司,不过微软也计划采购 AMD 等其它公司的 GPU 进行扩充。

关键字: 微软 GPU

业内消息,微软公司宣布将向阿联酋顶级人工智能公司G42投资15亿美元,以支持这家总部位于阿布扎比的公司缩减在中国业务的承诺。此前该公司表示将减少在中国的业务,并承诺投资于主要的西方市场。

关键字: 微软 G42

4月17日消息,近日微软硬件设计领域的重量级人物、Surface设计团队负责人Ralf Groene在领英上发帖宣布退休。

关键字: 微软 AI 机器AI 比尔盖茨

英国竞争与市场管理局(CMA)日前表示,对美国科技公司可能会操纵全球AI市场感到担忧。

关键字: AI 谷歌 苹果 微软 Meta

据韩联社报道,上周三星电子发布业绩报告显示,随着芯片价格反弹,预计今年第一季度营业利润同比骤增931.25%,为6.6万亿韩元(当前约合人民币354.6亿元),已经超过了2023年全年营业利润6.57万亿韩元。

关键字: 内存 三星

TDK 株式会社(TSE:6762)进一步扩充 Micronas 嵌入式电机控制器系列 HVC 5x,完全集成电机控制器与 HVC-5222D 和 HVC-5422D,以驱动小型有刷(BDC)、无刷(BLDC)或步进电机...

关键字: 嵌入式 电机控制器 内存

Apr. 04, 2024 ---- TrendForce集邦咨询针对403震后各半导体厂动态更新,由于本次地震大多晶圆代工厂都位属在震度四级的区域,加上台湾地区的半导体工厂多以高规格兴建,内部的减震措施都是世界顶尖水平...

关键字: 晶圆代工 内存

英特尔近日向媒体透露,微软的Copilot AI将很快运行在本地PC上,而不是依赖云端。

关键字: 英特尔 微软 Copilot AI
关闭
关闭