打击内存泄漏

时间:2011-01-27 17:29:17

标签: c++ visual-studio visual-studio-2010

内存泄漏是一场噩梦。我知道:我有一些。

找到它们的最有效(最痛苦和最耗时)的方法是什么?

我使用Visual Studio 2010并使用C ++ for Windows进行开发。

8 个答案:

答案 0 :(得分:4)

Smart Pointers可能就是你要找的东西。至少在分配对象时。

对于容器,尽可能使用STL。

如果您正在寻找现有的内存泄漏,如果您在VS2010中,可以使用DEBUG_NEW

答案 1 :(得分:4)

我已将Valgrind用于Linux,但您可以在此处找到一些有用的链接Is there a good Valgrind substitute for Windows?

答案 2 :(得分:2)

如果您的代码目前有很多对newdelete的调用,并且您可以使用boost(或tr1),则以下类型对于防止泄漏非常有帮助

  • boost::shared_ptr
  • boost::scoped_ptr
  • boost::shared_array
  • boost::scoped_array
如果您无法使用提升,则

boost替换为std::tr1。推特网站上提供了更多documentation

答案 3 :(得分:2)

对于MSVC我使用以下技术:

  1. 使用预编译的标头(例如 stdafx.h)在一开始 stdafx.h put:

    #if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
        #define _CRTDBG_MAP_ALLOC
        #include <crtdbg.h>
        #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__)
    #endif
    
  2. 然后包括您不会修改的所有标题(或至少非常少)。这里的要点是包含重载operator new

  3. 的标头
  4. 在stdafx.h结尾处放了:

    #if defined(_WIN32) && defined(_DEBUG) && defined(_MSC_VER)
        #if defined(new)
            #undef new
        #endif
        #define new DEBUG_NEW
    #endif
    
  5. main()放置

    _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
    

    作为第一行。

  6. 试验:

    int main()
    {
        _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF/* | _CRTDBG_CHECK_CRT_DF*/);
         int* i = new int();
         std::vector<int>* v =  new std::vector<int>(1000);
         return 0;
    }
    

    在调试器下运行它。在VC输出窗口,你会发现类似的东西:

    The thread 'Win32 Thread' (0x1b1c) has exited with code 0 (0x0).
    Detected memory leaks!
    Dumping objects ->
    {97} normal block at 0x02725E38, 4000 bytes long.
     Data: <                > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
    c:\main.cpp(7) : {96} normal block at 0x02725DE8, 20 bytes long.
     Data: <        8^r  mr > 00 00 00 00 CD CD CD CD 38 5E 72 02 D8 6D 72 02 
    c:\main.cpp(6) : {95} normal block at 0x02725DA8, 4 bytes long.
     Data: <    > 00 00 00 00 
    Object dump complete.
    The program '[3756] test.exe: Native' has exited with code 0 (0x0).
    

    如你所见,有3个memleaks:main()中的两个和其他地方的一个。 “其他地方”未被检测到,因为它位于<vector>中,并且已包含在#define new DEBUG_NEW之前。这个报告很方便,因为在调试器中每次运行后你都会看到是否出现了新的memleak,你可以点击相应的memleak来转到编辑器中的确切位置。 {97}{96}等示例中的数字表示内存分配的序列号,您可以通过在之前放置_CrtSetBreakAlloc(memalloc_seq_no); 来使用它来触发相应的内存分配分配确实发生了。

    这项技术远非完美和普及,但我发现它在VC的日常编程中非常有用。

    MSDN

    中的更多信息

答案 4 :(得分:2)

在进入调试之前,让我们考虑设计。

有两个基本规则可以避免内存泄漏:

  1. 不要使用new
  2. 不要使用delete
  3. 起初看起来可能会令人惊讶:)

    <强> 1。请勿使用new

    只要你真的可以避免它,就不应该在堆上进行分配:

    • 在堆栈上分配
    • 使用将为您处理内存分配的容器

    您从代码中删除的每个new都必然是您没有的内存泄漏。

    <强> 2。请勿使用delete

    问题是将每个newdelete相匹配是非常困难的。记住删除它很困难,很难不删除它两次。

    C ++以SBRM的形式提供了一个方便的解决方案(Scoped-Bound Resources Management,也称为RAII缩写,其名称相当误导)。这尤其意味着聪明的经理

    每当你实际调用new时,请确保立即将管理新分配对象的责任传递给智能指针或其他同样智能的容器(例如boost::ptr_vector)。

    第3。代码审核

    • 质疑new
    • 的每次使用
    • 确保每个new结果立即置于智能管理器中

    注意:delete的唯一位置是您编写智能经理时。考虑到广泛的选择,通常没有什么意义。但是,如果你这样做,请记住它是大师区(例外情况安全......);我能给出的一个建议是,智能经理只做一件事:它巧妙地管理一个单一资源。尝试将它与其他东西结合起来,你会失败,你甚至都不会意识到它

答案 5 :(得分:1)

我正在完成我的程序并减少使用智能指针以及单例。

这个想法是仅在必要时使用指针和智能指针。

我使用这两种技术清理了大量内存泄漏。例如,如果类需要访问单例,则将单例转换为常规对象并作为参数传递。这具有显示方法和类的真实要求的副作用,而不是具有隐藏的单例要求。

答案 6 :(得分:1)

如果您可以重现该问题,我建议您使用Windows调试工具包中的umdh。如果使用gflags或应用程序验证程序为可执行文件打开用户模式堆栈跟踪,则可以使用所有内存分配的快照之前和之后,然后可以使用它们来跟踪它们。您还可以使用windbg附加并使用!heap -l列出任何未引用的分配,并可以使用!heap -p -a打印它们。

您还可以使用像Coverity这样的静态分析工具(不确定是否有任何好的免费替代方案),但它可能无法捕捉到所有情况。

答案 7 :(得分:0)

对内存泄漏的世界统治的良好计划是根本不使用堆。

而不是:

class A { A() { m_a = new int; } ~A() { delete m_a; } int *m_a; };

总是可以使用:

class A { int m_a; };

它更简单,更方便。

它也适用于其他情况,而不是:

MyObject *create_obj(int a) { 
   switch(a) { 
      case 0: return new MyDerived1; 
      case 1: return new MyDerived2; 
   };
}
int main() { MyObject *o = create_obj(1); o->do_x(); o->do_y(); delete o; }

可以随时使用:

class Obj {
   Obj(int a) : a(a) { }
   void do_x() { switch(a) { case 0: ... break; case 1: ... break; }; }
   void do_y() { switch(a) { case 0: ... break; case 1: ... break; }; }
   int a;
}; 
int main() { Obj o(1); o.do_x(); o.do_y(); }

将大型数据结构从作用域复制到另一个作为堆栈分配的工作原理如下:

void Collect(MyStorage &s)
{
   MyObject o;
   MyObject2 o2(o);
   MyObject3 o3(o2);
   s.Copy(o3);
}
void f(const MyStorage &s);
int main() { MyStorage s; Collect(s); f(s); }

这样在堆栈上创建大型数据结构就可以了。 Storage类需要堆。