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

Chr*_*_vr 157 c++ memory-leaks

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

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

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

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

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

Joh*_*ith 244

说明

你需要的东西

  • 熟练掌握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.
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

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!
Run Code Online (Sandbox Code Playgroud)

4

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

void Leak(int x){

char* p = new char [x];

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

}
Run Code Online (Sandbox Code Playgroud)

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

char* one = new char;

delete [] one; // Wrong

char* many = new char [30];

delete many; // Wrong!
Run Code Online (Sandbox Code Playgroud)

6

如果泄漏仍然允许 - 我通常会与deleaker一起寻找它(请在此处查看:http://deleaker.com ).

谢谢!

  • 抱歉问题 - 评论,但没有指针的功能参数怎么样?`someFunction("some parameter")`在函数调用之后,我必须删除`someFunction`中的``some parameter``,还是自动删除? (3认同)

Naw*_*waz 31

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

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

#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW
Run Code Online (Sandbox Code Playgroud)

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

并且__FILE__,__LINE__预定义的宏,分别评估您使用它们的文件名和行号!

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

跨平台内存泄漏检测器


来自Wikpedia,

Debug_new指的是C++中的一种技术,用于重载和/或重新定义operator new和operator delete,以拦截内存分配和释放调用,从而调试程序以便内存使用.它通常涉及定义名为DEBUG_NEW的宏,并使new变为new(_ FILE _,_ LINE _)以记录分配时的文件/行信息.Microsoft Visual C++在其Microsoft基础类中使用此技术.有一些方法可以扩展此方法以避免使用宏重定义,同时仍然能够在某些平台上显示文件/行信息.这种方法有许多固有的局限性.它仅适用于C++,并且不能捕获像malloc这样的C函数的内存泄漏.但是,与一些更完整的内存调试器解决方案相比,它使用起来非常简单,速度也非常快.

  • 这个`#define`会搞乱重载的`operator new`并产生编译器错误.即使您成功克服了这一点,仍然无法解决重载功能.虽然技术很好,但有时需要进行大量的代码更改. (4认同)

Doc*_*own 14

有一些众所周知的编程技术可以帮助您最大限度地降低第一手获取内存泄漏的风险:

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


Cir*_*四事件 9

自动内存泄漏检查器的调查

在这个答案中,我在一个简单易懂的内存泄漏示例中比较了几种不同的内存泄漏检查器。

在此之前,请参阅 ASan wiki 中的这张大表,其中比较了人类已知的所有工具:https : //github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools/d06210f759fec97066888e5f27c7e722832b0924

分析的例子将是:

主文件

#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);
}
Run Code Online (Sandbox Code Playgroud)

GitHub 上游.

我们将尝试看看不同的工具如何清楚地将我们指向泄漏调用。

来自谷歌 gperftools 的 tcmalloc

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

并且输出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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

我不知道为什么第三个没有出现

无论如何,通常当某些东西泄漏时,它会发生很多次,而当我在实际项目中使用它时,我最终很容易被指出泄漏功能。

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

更多文档在:

另请参阅:如何使用 TCMalloc?

在 Ubuntu 19.04、google-perftools 2.5-2 中测试。

Address Sanitizer (ASan) 也由 Google 提供

https://github.com/google/sanitizers

之前在:如何在 C++ 代码/项目中找到内存泄漏?TODO 与 tcmalloc。

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

gcc -fsanitize=address -ggdb3 -o main.out main.c
./main.out 
Run Code Online (Sandbox Code Playgroud)

和执行输出:

=================================================================
==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).
Run Code Online (Sandbox Code Playgroud)

它清楚地标识了所有泄漏。好的!

ASan 还可以执行其他很酷的检查,例如越界写入:检测到堆栈粉碎

在 Ubuntu 19.04、GCC 8.3.0 中测试。

瓦尔格林德

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
Run Code Online (Sandbox Code Playgroud)

输出:

==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)
Run Code Online (Sandbox Code Playgroud)

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

另请参阅:如何使用 valgrind 查找内存泄漏?

在 Ubuntu 19.04、valgrind 3.14.0 中测试。


Aar*_*otz 8

  1. 下载适用于Windows的调试工具.
  2. 使用该gflags实用程序打开用户模式堆栈跟踪.
  3. UMDH把你的程序的内存的多个快照.在分配内存之前拍摄快照,并在您认为程序泄漏内存之后拍摄第二个快照.您可能希望在程序中添加暂停或提示,以便您有机会运行UMDH并拍摄快照.
  4. UMDH再次运行,这次是在两个快照之间进行差异的模式.然后它将生成一个报告,其中包含可疑内存泄漏的调用堆栈.
  5. 完成后恢复以前的gflags设置.

UMDH将提供比CRT调试堆更多的信息,因为它正在整个过程中观察内存分配; 它甚至可以告诉您第三方组件是否泄漏.

  • 我更喜欢 Deleaker 和 Valgrind 而不是标准的分析器 (2认同)

Gal*_*ana 7

运行"Valgrind"可以:

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

2)指出错误的释放内存的尝试(例如,"删除"的不正确调用)

使用"Valgrind"的说明

1)在这里获得valgrind .

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

3)在你的shell运行中:

valgrind --leak-check=yes myprog arg1 arg2
Run Code Online (Sandbox Code Playgroud)

"myprog"是你编译的程序,"arg1","arg2"你的程序的参数.

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

例如:

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

==4230==    by 0x804990F: main (example.c:6)
Run Code Online (Sandbox Code Playgroud)

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

正如其他人指出的那样,请确保每次"新"/"malloc"通话都有后续的"删除"/"免费"通话.


Beg*_*ner 6

AddressSanitizer (ASan) 是一种快速内存错误检测器。它发现 C/C++ 程序中的释放后使用和 {heap,stack,global}-buffer 溢出错误。它发现:

  • 释放后使用(悬空指针取消引用)
  • 堆缓冲区溢出
  • 堆栈缓冲区溢出
  • 全局缓冲区溢出
  • 退货后使用
  • 初始化顺序错误

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


小智 6

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

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

`$` valgrind ./your_CPP_program 
Run Code Online (Sandbox Code Playgroud)


Lel*_*mbo 5

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

我想知道程序员如何发现内存泄漏

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

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

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


Man*_*rse 5

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


Ben*_*jia 5

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

  2. 这个论坛讨论了一些避免C/C++内存泄漏的方法.


归档时间:

查看次数:

212612 次

最近记录:

5 年,11 月 前