如何在C ++代码/项目中查找内存泄漏?

时间:2011-06-07 06:09:04

标签: c++ memory-leaks

我是Windows平台上的C ++程序员。我正在使用Visual Studio 2008。

我通常会在内存泄漏的代码中结束。

通常我会通过检查代码来发现内存泄漏,但这很麻烦,并不总是一个好方法。

由于我买不起付费内存泄漏检测工具,我希望你们建议尽可能避免内存泄漏的方法。

  1. 我想知道程序员如何找到内存泄漏。
  2. 是否应遵循任何标准或程序以确保程序中没有内存泄漏?

19 个答案:

答案 0 :(得分:225)

<强>说明

你需要的东西

  • 熟练掌握C ++
  • C ++编译器
  • 调试器和其他调查软件工具

1

了解运营商的基础知识。 C ++运算符“new”分配堆内存。 “删除”操作符释放堆内存。对于每个“新”,您应该使用“删除”,以便释放您分配的相同内存:

char* str = new char [30]; // Allocate 30 bytes to house a string.

delete [] str; // Clear those 30 bytes and make str point nowhere.

2

仅在您删除时才重新分配内存。在下面的代码中,str使用第二个分配获取新地址。第一个地址无法恢复,它所指向的30个字节也是如此。现在他们不可能自由,而且你有内存泄漏:

char* str = new char [30]; // Give str a memory address.

// delete [] str; // Remove the first comment marking in this line to correct.

str = new char [60]; /* Give str another memory address with
                                                    the first one gone forever.*/

delete [] str; // This deletes the 60 bytes, not just the first 30.

3

观察指针分配。每个动态变量(堆上分配的内存)都需要与指针相关联。当动态变量与其指针解除关联时,将无法擦除。同样,这会导致内存泄漏:

char* str1 = new char [30];

char* str2 = new char [40];

strcpy(str1, "Memory leak");

str2 = str1; // Bad! Now the 40 bytes are impossible to free.

delete [] str2; // This deletes the 30 bytes.

delete [] str1; // Possible access violation. What a disaster!

4

小心本地指针。您在函数中声明的指针在堆栈上分配,但它指向的动态变量在堆上分配。如果你不删除它,它将在程序退出函数后持续存在:

void Leak(int x){

char* p = new char [x];

// delete [] p; // Remove the first comment marking to correct.

}

5

“删除”后注意方括号。单独使用“删除”来释放单个对象。使用方括号“delete”[]释放堆数组。不要做这样的事情:

char* one = new char;

delete [] one; // Wrong

char* many = new char [30];

delete many; // Wrong!

6

如果泄漏仍然允许 - 我通常会在删除程序中查找它(请在此处查看:http://deleaker.com)。

谢谢!

答案 1 :(得分:28)

您可以在代码中使用某些技术来检测内存泄漏。最常见,最简单的检测方法是,定义一个宏,例如DEBUG_NEW并使用它,以及预定义的宏,如__FILE____LINE__,以找到代码中的内存泄漏。这些预定义的宏告诉您内存泄漏的文件和行号。

DEBUG_NEW只是一个MACRO,通常定义为:

#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW

因此,无论您在何处使用new,它都可以跟踪可用于查找程序中内存泄漏的文件和行号。

__FILE____LINE__predefined macros,分别评估您使用它们的文件名和行号!

阅读以下文章,该文章解释了将DEBUG_NEW与其他有趣的宏一起使用的技巧,非常漂亮:

A Cross-Platform Memory Leak Detector


来自Wikpedia

  

Debug_new是指C ++中的一种技术   过载和/或重新定义操作员   new和operator delete为了   拦截内存分配和   解除分配调用,从而调试一个   内存使用程序。 经常   涉及定义一个名为的宏   DEBUG_NEW,让新成为   像new(_ FILE _,_ LINE _)   记录文件/行信息   分配。 Microsoft Visual C ++使用   这种技术在微软   基础课程。有一些   扩展此方法以避免的方法   仍然使用宏重新定义   能够显示文件/行   某些平台上的信息。那里   这有很多固有的局限性   方法。它仅适用于C ++和   无法通过C捕获内存泄漏   像malloc这样的函数。但是,它可以   使用非常简单,而且非常简单   比较快一些   完整的内存调试解决方案。

答案 2 :(得分:14)

有一些众所周知的编程技术可以帮助您将第一手内存泄漏的风险降至最低:

  • 如果您必须自己动态分配内存,请始终成对编写newdelete,并确保分配/解除分配代码成对调用
  • 如果可以,请避免动态内存分配。例如,尽可能使用vector<T> t而不是T* t = new T[size]
  • 使用“智能指针”,如提升智能指针(http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/smart_ptr.htm
  • 我个人的最爱:确保你已经理解了指针所有权的概念,并确保在使用指针的任何地方,你知道哪个代码实体是所有者
  • 了解哪些构造函数/赋值运算符是由C ++编译器自动创建的,如果你有一个拥有指针的类意味着什么(或者如果你有一个包含指向对象的指针的类那么意味着什么呢? >不拥有)。

答案 3 :(得分:8)

  1. 下载Debugging Tools for Windows
  2. 使用gflags实用程序打开用户模式堆栈跟踪。
  3. 使用UMDH拍摄程序内存的多个快照。在分配内存之前拍摄快照,并在您认为程序泄漏内存之后拍摄第二个快照。您可能希望在程序中添加暂停或提示,以便有机会运行UMDH并拍摄快照。
  4. 再次运行UMDH,这次是在两个快照之间进行差异的模式。然后它将生成一个报告,其中包含可疑内存泄漏的调用堆栈。
  5. 完成后恢复以前的gflags设置。
  6. UMDH将为您提供比CRT调试堆更多的信息,因为它正在监视整个过程中的内存分配;它甚至可以告诉你第三方组件是否泄漏。

答案 4 :(得分:8)

答案 5 :(得分:7)

跑步&#34; Valgrind&#34;可以:

1)帮助识别内存泄漏 - 显示您有多少内存泄漏,并指出代码中泄漏内存的行。

2)指出错误的释放内存的尝试(例如&#34的错误调用;删除&#34;)

使用&#34; Valgrind&#34;

的说明

1)获取valgrind here

1)使用-g标志编译代码

3)在你的shell中运行:

valgrind --leak-check=yes myprog arg1 arg2

在哪里&#34; myprog&#34;是你编译的程序&#34; arg1&#34;,&#34; arg2&#34;你的程序的论点。

4)结果是一个对malloc / new的调用列表,后者没有后续的免费删除调用。

例如:

==4230==    at 0x1B977DD0: malloc (vg_replace_malloc.c:136)

==4230==    by 0x804990F: main (example.c:6)

告诉你在哪一行调用了malloc(未被释放)。

正如其他人指出的那样,确保为每一个&#34; /&#34; malloc&#34;打电话给你后续&#34;删除&#34; /&#34;免费&#34;调用

答案 6 :(得分:5)

如果您使用gcc,则可以使用gprof。

  

我想知道程序员如何找到内存泄漏

有些人使用工具,有些人使用工具,也可以通过同行代码审查

  

是否应遵循任何标准或程序以确保程序中没有内存泄漏

对我来说:每当我创建动态分配的对象时,我总是将释放代码放在之后,然后填充代码。如果您确定代码之间不会有例外,那就没问题。否则,我会使用try-finally(我不经常使用C ++)。

答案 7 :(得分:5)

在代码中搜索new的出现次数,并确保它们都出现在析构函数中具有匹配删除的构造函数中。确保这是该构造函数中唯一可能抛出的操作。一种简单的方法是将所有指针包装在std::auto_ptrboost::scoped_ptr中(取决于您是否需要移动语义)。对于所有未来的代码,只需确保每个资源都由一个对象拥有,该对象在其析构函数中清理资源。如果您需要移动语义,那么您可以升级到支持r值引用的编译器(我相信VS2010)并创建移动构造函数。如果您不想这样做,那么您可以使用各种棘手的技术,包括尽职尽责地使用swap,或者尝试使用Boost.Move库。

答案 8 :(得分:5)

  1. 在visual studio中,有一个内置的内存泄漏检测器,叫做C Runtime Library。当主函数返回后程序退出时,CRT将检查应用程序的调试堆。如果你仍然在调试堆上分配了任何块,那么你有内存泄漏..

  2. This forum讨论了一些避免C / C ++内存泄漏的方法..

答案 9 :(得分:4)

Visual Leak Detector (VLD)是一个免费,强大的开源内存泄漏检测系统,适用于Visual C ++。

  

在Visual Studio调试器下运行程序时,Visual Leak Detector将在调试会话结束时输出内存泄漏报告。泄漏报告包括完整调用堆栈,显示如何分配泄漏的内存块。双击调用堆栈中的一行以跳转到该文件并在编辑器窗口中行。

如果您只有崩溃转储,则可以使用Windbg !heap -l命令,它将检测泄漏的堆块。最好打开gflags选项:“创建用户模式堆栈跟踪数据库”,然后你会看到内存分配调用堆栈。

答案 10 :(得分:3)

在Windows上,您可以使用CRT debug heap

  

是否应遵循任何标准或程序以确保程序中没有内存泄漏。

是的,不要使用手动内存管理(如果你手动调用deletedelete[],那么你做错了)。使用RAII和智能指针,将堆分配限制在绝对最小值(大多数情况下,自动变量就足够了)。

答案 11 :(得分:3)

您可以使用Valgrind工具检测内存泄漏。

另外,要查找特定函数中的泄漏,请在函数末尾使用exit(0),然后使用Valgrind运行它

public static Customer merge(Customer first, Customer second) {
        Customer customer = new Customer(first.getName(), first.getTotal(), first.getBalance());
        customer.setTotal(customer.getTotal() + second.getTotal());
        customer.setBalance(customer.getBalance() + second.getBalance());
        return customer;
}

答案 12 :(得分:3)

MTuner是一个免费的多平台内存分析,泄漏检测和分析工具,支持MSVC,GCC和Clang编译器。功能包括:

  • 基于时间轴的内存使用历史记录和实时内存块
  • 基于堆,内存标签,时间范围等的强大内存操作过滤
  • 完整源代码的手动检测SDK
  • 通过命令行使用提供持续集成支持
  • 调用堆栈树和树图导航
  • 更多。

用户可以使用GCC或Clang cross分析任何软件定位平台 编译器。 MTuner内置支持Windows,PlayStation 4和PlayStation 3平台。

答案 13 :(得分:3)

AddressSanitizer(ASan)是一个快速记忆错误检测器。 它在C / C ++程序中找到了use-after-free和{heap,stack,global} -buffer溢出错误。它发现:

  • 免费使用(悬空指针取消引用)
  • 堆缓冲区溢出
  • 堆栈缓冲区溢出
  • 全局缓冲区溢出
  • 返回后使用
  • 初始化订单错误

这个工具非常快。仪表程序的平均减速度约为2倍。

答案 14 :(得分:3)

回答你问题的第二部分,

  

是否应遵循任何标准或程序以确保程序中没有内存泄漏。

是的,有。这是C和C ++之间的主要区别之一。

在C ++中,您不应该在用户代码中调用newdeleteRAII是一种非常常用的技术,它几乎解决了资源管理问题。程序中的每个资源(资源是必须获取的任何资源,然后发布:文件句柄,网络套接字,数据库连接,还有普通内存分配,在某些情况下,还有一对API调用(BeginX( )/ EndX(),LockY(),UnlockY()),应该包装在一个类中,其中:

  • 构造函数获取资源(如果资源是memroy分配,则通过调用new
  • 析构函数发布资源
  • 要么阻止复制和赋值(通过使复制构造函数和赋值运算符成为私有),要么实现复制和赋值以正常工作(例如通过克隆底层资源)

然后通过调用new并存储指针,在本地,堆栈或类成员上实例化此类,并且

您通常不需要自己定义这些类。标准库容器也以这种方式运行,因此当向量被销毁时,存储在std::vector中的任何对象都会被释放。所以,再次,不要将指针存储到容器中(这需要来调用newdelete),而是将对象本身(它为您提供免费的内存管理 )。同样,智能指针类可以用来轻松地包装只需要用new分配的对象,并控制它们的生命周期。

这意味着当对象超出范围时,它会自动销毁,并释放和清理其资源。

如果您在整个代码中始终如一地执行此操作,则不会有任何内存泄漏。 可能泄漏的所有内容都与析构函数相关联,当控件离开声明对象的作用域时,该析构函数将被保证。

答案 15 :(得分:2)

人类已知的所有工具的比较

在此之前,请参阅ASan Wiki中的这张大表:https://github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools/d06210f759fec97066888e5f27c7e722832b0924

Google的

tcmalloc来自gperftools

https://github.com/gperftools/gperftools

在Ubuntu 19.04上的用法:

sudo apt-get install google-perftools
gcc -ggdb3 -o main.out main.c -ltcmalloc
PPROF_PATH=/usr/bin/google-pprof \
  HEAPCHECK=normal \
  HEAPPROFILE=ble \
  ./main.out \
;
google-pprof main.out ble.0001.heap --text

简单的测试程序:

main.c

#include <stdlib.h>

void * my_malloc(size_t n) {
    return malloc(n);
}

void leaky(size_t n, int do_leak) {
    void *p = my_malloc(n);
    if (!do_leak) {
        free(p);
    }
}

int main(void) {
    leaky(0x10, 0);
    leaky(0x10, 1);
    leaky(0x100, 0);
    leaky(0x100, 1);
    leaky(0x1000, 0);
    leaky(0x1000, 1);
}

GitHub upstream

程序运行的输出包含内存泄漏分析:

WARNING: Perftools heap leak checker is active -- Performance may suffer
Starting tracking the heap
Dumping heap profile to ble.0001.heap (Exiting, 4 kB in use)
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 272 bytes in 2 objects
The 2 largest leaks:
Using local file ./main.out.
Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start


If the preceding stack traces are not enough to find the leaks, try running THIS shell command:

pprof ./main.out "/tmp/main.out.24744._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more re
Exiting with error code (instead of crashing) because of whole-program memory leaks

google-pprof的输出包含堆使用情况分析:

Using local file main.out.
Using local file ble.0001.heap.
Total: 0.0 MB
     0.0 100.0% 100.0%      0.0 100.0% my_malloc
     0.0   0.0% 100.0%      0.0 100.0% __libc_start_main
     0.0   0.0% 100.0%      0.0 100.0% _start
     0.0   0.0% 100.0%      0.0 100.0% leaky
     0.0   0.0% 100.0%      0.0 100.0% main

输出将我们指向三个泄漏中的两个:

Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start

我不确定为什么第三个没有出现

在任何情况下,通常当某件事泄漏时,它会发生很多次,而当我在真实的项目中使用它时,我最终很容易被指出泄漏函数。

正如输出本身所提到的,这会导致执行速度显着降低。

更多文档,位于:

另请参阅:How To Use TCMalloc?

在Ubuntu 19.04,google-perftools 2.5-2中进行了测试。

地址消毒剂(ASan)也由Google

https://github.com/google/sanitizers

先前在How to find memory leak in a C++ code/project? TODO与tcmalloc中提到。

这已集成到GCC中,因此您可以执行以下操作:

gcc -fsanitize=address -ggdb3 -o main.out main.c
./main.out 

和执行输出:

=================================================================
==27223==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4096 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f210 in main /home/ciro/test/main.c:20
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 256 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1f2 in main /home/ciro/test/main.c:18
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1d4 in main /home/ciro/test/main.c:16
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

SUMMARY: AddressSanitizer: 4368 byte(s) leaked in 3 allocation(s).

可清楚地识别所有泄漏。很好!

ASan还可以进行其他很酷的检查,例如越界写入:Stack smashing detected

在Ubuntu 19.04,GCC 8.3.0中进行了测试。

Valgrind

http://www.valgrind.org/

先前在https://stackoverflow.com/a/37661630/895245

中提到

用法:

sudo apt-get install valgrind
gcc -ggdb3 -o main.out main.c
valgrind --leak-check=yes ./main.out

输出:

==32178== Memcheck, a memory error detector
==32178== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==32178== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==32178== Command: ./main.out
==32178== 
==32178== 
==32178== HEAP SUMMARY:
==32178==     in use at exit: 4,368 bytes in 3 blocks
==32178==   total heap usage: 6 allocs, 3 frees, 8,736 bytes allocated
==32178== 
==32178== 16 bytes in 1 blocks are definitely lost in loss record 1 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091B4: main (main.c:16)
==32178== 
==32178== 256 bytes in 1 blocks are definitely lost in loss record 2 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091D2: main (main.c:18)
==32178== 
==32178== 4,096 bytes in 1 blocks are definitely lost in loss record 3 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091F0: main (main.c:20)
==32178== 
==32178== LEAK SUMMARY:
==32178==    definitely lost: 4,368 bytes in 3 blocks
==32178==    indirectly lost: 0 bytes in 0 blocks
==32178==      possibly lost: 0 bytes in 0 blocks
==32178==    still reachable: 0 bytes in 0 blocks
==32178==         suppressed: 0 bytes in 0 blocks
==32178== 
==32178== For counts of detected and suppressed errors, rerun with: -v
==32178== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

因此,再次检测到所有泄漏。

另请参阅:How do I use valgrind to find memory leaks?

在Ubuntu 19.04,valgrind 3.14.0中进行了测试。

答案 16 :(得分:0)

除了其他anwers中提供的工具和方法之外,静态代码分析工具还可用于检测内存泄漏(以及其他问题)。 一个免费的强大工具是Cppcheck。但是还有很多其他工具可供使用。 Wikipedia有一个静态代码分析工具列表。

答案 17 :(得分:0)

确保所有堆内存都已成功释放。如果您从不在堆上分配内存,则没有必要。如果这样做,请计算分配内存的次数,并计算释放内存的次数。

答案 18 :(得分:-1)

既不是新的&#34;或&#34;删除&#34;应该在应用程序代码中使用。相反,创建一个使用manager / worker惯用法的新类型,其中manager类分配并释放内存并将所有其他操作转发给worker对象。

不幸的是,这比应该做的更多,因为C ++没有超载#34;运算符。&#34;。在存在多态性的情况下,它的工作量更大。

但这是值得的,因为你不必担心内存泄漏,这意味着你甚至不必去寻找它们。