我是Windows平台上的C ++程序员。我正在使用Visual Studio 2008。
我通常会在代码中出现内存泄漏。
通常,我可以通过检查代码来发现内存泄漏,但这很麻烦,而且并不总是一种好的方法。
由于我买不起付费的内存泄漏检测工具,因此我希望你们提出避免内存泄漏的最佳方法。
- 我想知道程序员如何发现内存泄漏。
- 是否应遵循任何标准或程序以确保程序中没有内存泄漏?
我是Windows平台上的C ++程序员。我正在使用Visual Studio 2008。
我通常会在代码中出现内存泄漏。
通常,我可以通过检查代码来发现内存泄漏,但这很麻烦,而且并不总是一种好的方法。
由于我买不起付费的内存泄漏检测工具,因此我希望你们提出避免内存泄漏的最佳方法。
Answers:
使用说明
您需要的东西
了解操作员的基本知识。C ++运算符new
分配堆内存。该delete
运营商的FreeS堆内存。对于each new
,您应该使用a,delete
以便释放分配的相同内存:
char* str = new char [30]; // Allocate 30 bytes to house a string.
delete [] str; // Clear those 30 bytes and make str point nowhere.
仅在删除后才重新分配内存。在下面的代码中,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.
观看那些指针分配。每个动态变量(堆上已分配的内存)都需要与一个指针相关联。当动态变量与其指针解除关联时,将无法擦除。同样,这会导致内存泄漏:
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!
注意局部指针。您在函数中声明的指针分配在堆栈上,但是它指向的动态变量分配在堆上。如果不删除它,它将在程序退出该函数后继续存在:
void Leak(int x){
char* p = new char [x];
// delete [] p; // Remove the first comment marking to correct.
}
注意“删除”后的方括号。delete
单独使用以释放单个对象。delete []
与方括号一起使用以释放堆数组。不要做这样的事情:
char* one = new char;
delete [] one; // Wrong
char* many = new char [30];
delete many; // Wrong!
如果泄漏尚未被允许-我通常会与deleaker一起寻求它(请在此处进行检查:http ://deleaker.com )。
someFunction("some parameter")
我不得不删除"some parameter"
的someFunction
,在函数调用后,或者在这些自动删除?
您可以在代码中使用某些技术来检测内存泄漏。最常见和最简单的检测方法是,定义一个宏DEBUG_NEW并使用它,以及预定义的宏(例如)__FILE__
并__LINE__
在代码中查找内存泄漏。这些预定义的宏告诉您内存泄漏的文件和行数。
DEBUG_NEW只是一个宏,通常定义为:
#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW
这样,无论您在哪里使用new
它,都可以跟踪文件和行号,这些文件和行号可用于定位程序中的内存泄漏。
而且__FILE__
,__LINE__
在预定义宏在您使用它们分别评估的文件名和行号!
阅读以下文章,它非常精美地介绍了将DEBUG_NEW与其他有趣的宏配合使用的技术:
从Wikpedia,
Debug_new是C ++中的一种技术,用于重载和/或重新定义运算符new和operator delete,以拦截内存分配和释放操作,从而调试程序以使用内存。它通常涉及定义一个名为DEBUG_NEW的宏,并使new成为类似于new(_ FILE _,_ LINE _)之类的东西,以记录有关分配的文件/行信息。Microsoft Visual C ++在其Microsoft基础类中使用了此技术。有一些方法可以扩展此方法,从而避免使用宏重新定义,同时仍然能够在某些平台上显示文件/行信息。这种方法有很多固有的局限性。它仅适用于C ++,并且无法通过malloc之类的C函数捕获内存泄漏。但是,与某些更完整的内存调试器解决方案相比,它使用起来非常简单,而且非常快。
#define
将导致过载operator new
并产生编译器错误。即使您成功克服了这一点,仍然无法解决重载的功能。尽管该技术不错,但有时仍需要大量代码更改。
有一些著名的编程技术可以帮助您最大程度地降低第一手获得内存泄漏的风险:
new
并delete
始终成对进行,并确保分配/解除分配代码成对调用vector<T> t
尽可能使用而不是T* t = new T[size]
gflags
实用程序打开用户模式堆栈跟踪。UMDH
把你的程序的内存的多个快照。在分配内存之前拍摄快照,并在您认为程序泄漏内存之后拍摄第二张快照。您可能希望在程序中添加暂停或提示,以便有机会运行UMDH
和拍摄快照。UMDH
再次运行,这次以在两个快照之间进行区别的模式运行。然后它将生成一个报告,其中包含可疑内存泄漏的调用堆栈。gflags
设置。UMDH
与CRT调试堆相比,它将为您提供更多信息,因为它正在监视整个过程中的内存分配;它甚至可以告诉您第三方组件是否泄漏。
运行“ Valgrind”可以:
1)帮助识别内存泄漏 -向您显示您有多少内存泄漏,并指出代码中分配泄漏内存的行。
2)指出释放内存的错误尝试(例如的不正确调用delete
)
使用“ Valgrind”的说明
1)在这里获取valgrind 。
2)用-g
标志编译代码
3)在您的shell中运行:
valgrind --leak-check=yes myprog arg1 arg2
其中“ myprog”是您的已编译程序,并且arg1
是arg2
您程序的参数。
4)结果是malloc
/ 的呼叫列表new
,没有随后的免费删除呼叫。
例如:
==4230== at 0x1B977DD0: malloc (vg_replace_malloc.c:136)
==4230== by 0x804990F: main (example.c:6)
告诉您在哪一行malloc
(未释放)被调用。
正如其他人指出的,请确保对于每个new
/ malloc
呼叫,您都有一个后续delete
/ free
呼叫。
如果您使用gcc,则可以使用gprof。
我想知道程序员如何发现内存泄漏
有些使用工具,有些做您的工作,也可以通过对等代码审查
是否应遵循任何标准或程序以确保程序中没有内存泄漏
对我来说:每当创建动态分配的对象时,我总是将释放代码放在后面,然后在它们之间填充代码。如果您确定两者之间的代码中没有异常,则可以。否则,我会尝试使用try-finally(我不经常使用C ++)。
在代码中搜索的出现new
,并确保它们全部在构造函数中发生,并且在析构函数中具有匹配的删除。确保这是该构造函数中唯一可能的抛出操作。一种简单的方法是将所有指针包装在std::auto_ptr
或中boost::scoped_ptr
(取决于您是否需要移动语义)。对于所有将来的代码,只需确保每个资源都由一个在其析构函数中清除该资源的对象拥有。如果需要移动语义,则可以升级到支持r值引用的编译器(我相信VS2010)并创建移动构造函数。如果您不想这样做,则可以使用各种棘手的技术,包括认真使用swap,或者尝试使用Boost.Move库。
scope_ptr
s,并且每个成员都分别进行初始化,则所有成功构建的成员将删除其指针,而其他成员仍将不持有指向已分配内存的指针。下班回家后的几个小时,我将举一个例子。
自动内存泄漏检查器概述
在这个答案中,我在一个简单易懂的内存泄漏示例中比较了几种不同的内存泄漏检查器。
首先,请参阅ASan Wiki中的这张巨大表格,其中比较了人类已知的所有工具:https : //github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools/d06210f759fec97066888e5f27c7e722832b0924
分析的示例将是:
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);
}
我们将尝试看看不同的工具将我们指向泄漏的呼叫有多清晰。
Google的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
程序运行的输出包含内存泄漏分析:
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
我不确定为什么第三个没有出现
无论如何,通常情况下,当某件事泄漏时,它会发生很多次,而当我在真实的项目中使用它时,我最终很容易被指出泄漏功能。
如输出本身所提到的,这会导致执行速度显着下降。
更多文档,请访问:
另请参阅:如何使用TCMalloc?
在Ubuntu 19.04中测试,google-perftools 2.5-2。
Google也提供地址消毒剂(ASan)
https://github.com/google/sanitizers
先前在以下文章中提到:如何在C ++代码/项目中查找内存泄漏?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还可以进行其他出色的检查,例如越界写入:检测到堆栈粉碎
已在Ubuntu 19.04,GCC 8.3.0中测试。
瓦尔格朗德
先前在以下位置提到过: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)
因此,再次检测到所有泄漏。
另请参阅:如何使用valgrind查找内存泄漏?
在Ubuntu 19.04,valgrind 3.14.0中进行了测试。
Visual Leak Detector(VLD)是一个免费的,健壮的,开放源代码的Visual C ++内存泄漏检测系统。
当您在Visual Studio调试器下运行程序时,Visual Leak Detector将在调试会话结束时输出内存泄漏报告。泄漏报告包括完整的调用堆栈,该堆栈显示了如何分配泄漏的内存块。双击调用堆栈中的一行以跳转到该文件,然后在编辑器窗口中一行。
如果只有崩溃转储,则可以使用Windbg !heap -l
命令,它将检测泄漏的堆块。最好打开gflags选项:“创建用户模式堆栈跟踪数据库”,然后您将看到内存分配调用堆栈。
MTuner是一个免费的多平台内存配置文件,泄漏检测和分析工具,支持MSVC,GCC和Clang编译器。功能包括:
用户可以使用GCC或Clang交叉编译器来分析针对平台的任何软件。MTuner内置了对Windows,PlayStation 4和PlayStation 3平台的支持。
回答问题的第二部分,
是否应遵循任何标准或程序以确保程序中没有内存泄漏。
就在这里。这是C和C ++之间的主要区别之一。
在C ++中,永远不要调用new
或delete
在用户代码中。RAII是一种非常常用的技术,几乎解决了资源管理问题。程序中的每个资源(资源是必须获取的所有资源,然后再发布:文件句柄,网络套接字,数据库连接,还包括普通内存分配,在某些情况下还包括成对的API调用(BeginX( )/ EndX(),LockY(),UnlockY())应该包装在一个类中,其中:
new
资源是否为内存分配)然后,可以在本地,在堆栈上或作为类成员实例化该类,而不是通过调用new
和存储指针来实例化。
您通常不需要自己定义这些类。标准库容器也以这种方式运行,以便std::vector
在销毁向量时释放存储在中的任何对象。同样,不要将指针存储在容器中(这将要求您调用new
和delete
),而是将对象本身(将为您提供免费的内存管理)存储。同样,智能指针类可用于轻松包装仅需使用分配的对象new
,并控制其生命周期。
这意味着,当对象超出范围时,它将被自动销毁,并释放和清理其资源。
如果您在整个代码中始终如一地执行此操作,则根本不会发生任何内存泄漏。这一切都可以控制叶子范围在该对象被宣布时调用得到泄露被绑定到这是保证析构函数。
AddressSanitizer(ASan)是一种快速内存错误检测器。它会在C / C ++程序中发现释放后使用和{heap,stack,global} -buffer溢出错误。它发现:
这个工具非常快。所检测程序的平均速度降低约2倍。
确保所有堆内存都已成功释放。如果您从不在堆上分配内存,则没有必要。如果这样做,请计算分配内存的次数,并计算释放内存的次数。