【问题标题】:memory leak: unable to break on a memory allocation number内存泄漏:无法中断内存分配号
【发布时间】:2010-06-25 14:21:29
【问题描述】:

我正在尝试查找内存泄漏问题。

我的项目是一个基于 ATL 的对话框项目,它使用 DirectShow 和标准库。

我的程序中总共有 45 个内存泄漏,每个都是 24 个字节。

我在我的 stdafx.h 中 #define'd _CRTDBG_MAP_ALLOC 等,以及 DEBUG_NEW 以获取每个内存泄漏的文件和行号。

但是,不会打印文件行号。内存块都是“普通”块,如下所示:

{180} normal block at 0x003E6008, 24 bytes long. Data: <  >  _>   > W   > A0 AE 3E 00 B0 5F 3E 00 A0 AE 3E 00 57 00 00 00 

我尝试在 _tWinMain() 的开头添加以下行

_CrtSetBreakAlloc(180);

为了中断分配,但调试器根本没有中断。

谁能告诉我如何追踪难以捉摸的内存泄漏?

最后,这是我的 _tWinMain() - 我在退出前调用了 _CrtDumpMemoryLeaks()。

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow){
    _CrtSetBreakAlloc(180);

    HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    ATLASSERT(SUCCEEDED(hRes));


    ::DefWindowProc(NULL, 0, 0, 0L);
    AtlInitCommonControls(ICC_BAR_CLASSES);

    //HINSTANCE hInstRich = ::LoadLibrary(CRichEditCtrl::GetLibraryName());

    hRes = _Module.Init(NULL, hInstance);
    ATLASSERT(SUCCEEDED(hRes));

    int nRet = Run(lpstrCmdLine, nCmdShow);

    _Module.Term();
    ::CoUninitialize();

    _CrtDumpMemoryLeaks();

    return nRet;
}

【问题讨论】:

  • 你在全局命名空间中有什么吗?有时,当您检查 memleaks 时,没有调用一些析构函数。另一种方法是用老式的方式来做,注释掉代码以本地化。
  • 顺便说一句,是的,如果你有任何静态变量指向任何堆分配的东西,并且在转储泄漏之前没有明确删除它们,那么肯定会报告泄漏。
  • 我会将其添加到回复中以进行格式化...

标签: c++ memory-leaks


【解决方案1】:

两个建议。

首先,在main(或等效)开始之前构建的内容在main 完成后被销毁。在main 末尾调用_CrtDumpMemoryLeaks 可能会给您带来误报。 (或者它们是假阴性?)全局对象的析构函数尚未运行,atexit 回调尚未运行,因此泄漏输出将包括尚未正确释放的分配。

(我怀疑这就是您的全局对象似乎泄漏的原因。代码很可能没有任何问题,实际上它很可能会正确清理自己 - 当_CrtDumpMemoryLeaks 时清理代码尚未运行正在被调用。)

您需要做的是在所有atexit 回调和全局对象析构函数完成之后,在最后指示运行时库为您调用_CrtDumpMemoryLeaks。然后你只会看到真正的泄漏。这个 sn-p 可以解决问题。坚持main开头:

_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)|_CRTDBG_LEAK_CHECK_DF);

其次,如果上面显示了在 main 之前运行的东西的真正泄漏,你可以做一些技巧来让你自己的一些代码在其他任何东西进入之前运行。然后你可以设置 @ 987654333@ 在任何分配发生之前。只需在自己的 .cpp 文件中弹出以下代码:

#include <crtdbg.h>

#ifdef _DEBUG

#pragma warning(disable:4074)//initializers put in compiler reserved initialization area
#pragma init_seg(compiler)//global objects in this file get constructed very early on

struct CrtBreakAllocSetter {
    CrtBreakAllocSetter() {
        _crtBreakAlloc=<allocation number of interest>;
    }
};

CrtBreakAllocSetter g_crtBreakAllocSetter;

#endif//_DEBUG

(我怀疑编译器的 init 段中的代码很可能在 stdinstdout 等被初始化之前运行,当然在构造任何全局对象之前,所以你可能很难做任何比以上!)

(对于它的价值,我最近的看法是,并且已经有一段时间了,main 开始之前的分配几乎总是一件坏事。让自己很难清理,并且很难跟踪正在发生的事情。这当然很方便,但您似乎总是最终会为它付出代价。尽管如此,分发比实施更容易的建议,特别是作为更大团队的一部分。 )

【讨论】:

  • 感谢您的意见。我完全同意在 main 之前分配是一件坏事。我更改了程序以在堆上分配全局对象,这减少了许多内存泄漏。剩下的几个只是我的粗心大意。我发现您的额外 cpp 文件很有趣 - 我以前没有阅读过使用 CrtBreakAllocSetter 的示例,我肯定会将它添加到我的工具集中...谢谢
  • 好建议。使用这种方法时,我仍然有 100 次泄漏。我不会在每一个上都设置一个断点。在我随机选择与 _CrtSetBreakAlloc() 一起使用的 10 个左右中,没有产生中断。这些泄漏块中的一些数据表明内存是在 dll 中分配的。首先,我测试了我是否在 malloc 看到了中断。那行得通。我无法想象它可能是什么(除了误报)。
  • 这正是问题所在:pklab.net/…
【解决方案2】:

我已经从程序中删除了几个全局变量(返回对错误消息的 CString 引用的查找表)。只要我这样做了 - 没有内存泄漏。

感谢你们的 cmets 人。

有趣 - 我将不得不研究一种不同的方法来实现错误查找。

我正在做类似的事情:

CString sError = "error at line x: " + g_map.lookup(hrError);

错误映射被实现为一个包装对 std::map 的访问的对象,它的析构函数工作正常。当我在堆上分配这个映射对象并释放它时,它报告没有内存泄漏。也许它是我连接 CString 的方式......

【讨论】:

  • 我理解希望您的代码无泄漏的纯粹性。请记住,只要(1)您没有长期运行的按需应用程序(例如,24/7/365 可用的服务)和(2)内存泄漏是有限的,它不是绝对有必要修复它们。
  • 嗨,马特 - 相反 - 无论在什么情况下,都绝不允许内存泄漏。这是一个最佳实践问题,从长远来看,当您的应用程序开发得更多时,您将不必追逐这些可能导致更大问题的泄漏。
  • @Poni,我同意最佳实践观点。但我仍然坚持我最初的说法,即在某些情况下,允许有限数量的内存泄漏。
  • 我认为两者都是有效点。根据高度计书(amazon.co.uk/Windows-PRO-Developer-Jeffrey-Nasarre-Christophe/…),当程序退出时,操作系统会清理任何内存泄漏,所以只要你确定它们不会增加,留下一些杂散字节应该是可以的操作系统进行清理。但是,这不是我的风格,我更愿意清理所有泄漏。但后来我正在处理的内存泄漏是 Windows 服务的一部分,在 20 分钟后消耗了超过 2Gb 的 RAM 并崩溃了。
  • @Tibo,我不反对。解决内存泄漏的原因有很多,但严格来说,并不总是必要的。例如,我的项目使用一些本土工具来测试交付产品的各个方面。这些工具不会像我们的产品一样严格。因此,如果发生内存泄漏等问题,花时间修复这些问题是没有道理的。是的,在它们发生时修复它们是错误的,但要认识到它们在任何情况下都不是世界末日。
猜你喜欢
  • 2016-09-12
  • 2010-12-21
  • 2019-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-11
相关资源
最近更新 更多