使用cuda出现内存泄露检测工具和访问越界的原因,但检查之后发现开辟内存之后也释放了啊,也没有越界

[转载]浅谈MFC内存泄露检测及内存越界访问保护机制【转载】
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&  本文所有代码均在VC2008下编译、调试。如果您使用的编译器不同,结果可能会有差别,但本文讲述的原理对于大部分编译器应该是相似的。对于本文的标题,实在不知道用什么表示更恰当,因为本文不仅淡了内存泄露检测机制,也谈到了指针越界的检测机制。到底应该说是MFC的机制,还是C++的机制?Anyway,相信你看了一定会有所收获。并欢迎常来本博客留言讨论。
  在我们开发MFC应用程序的时候,不知大家是否注意到Debug版本输出窗口经常会有下面这样的信息:
Detected memory leaks!
Dumping objects -&
c:my.datamy.codesmemleakmemleakmemleak.cpp(34) : {126} normal block at 0x00A321A0, 4 bytes long.
&Data: &&&& & 01 00 00 00
Object dump complete.
  编译器是怎么知道我们写的代码有内存泄露并能精确到文件、行号的呢?事实上也并不是所有情况都能精确到文件、行号,看看下面这种情况:
Detected memory leaks!
Dumping objects -&
First-chance exception at 0x75c739e5 (kernel32.dll) in MemLeak.exe:
0xC0000005: Access violation reading location 0x711af9f4.
#File Error#(62) : {137} normal block at 0x00A721A0, 4 bytes long.
&Data: &&&& & CD CD CD CD
Object dump complete.
  虽然检测出了内存泄露,但我们只能知道内存地址、行号,文件名是#File
Error#,而且还伴随着内存非法访问的异常。这个异常看似是MFC在检测内存泄露的时候产生的。
  下面我们从C++内存分配与回收的两个操作符new,
delete一步步分析C++内存管理以及MFC内存泄露检测机制。所有这些都是针对Debug版本的,最后我们再看看Release版本的情况。
一、内存分配操作符new  新建一个MFC应用程序,无论是Win32
Console Application + MFC Support,还是MFC Application或者是MFC
DLL。编译器为我们生成的代码最前面,在#include下面都会有下面这三行代码:
#ifdef _DEBUG
#define new DEBUG_NEW
  这三句话的意思是,如果是Debug版本,那么将new操作符定义为DEBUG_NEW。在afx.h中有对DEBUG_NEW的定义:
// Memory tracking allocation
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)
  看来MFC是重新定义了一个new操作符,并把文件名、行号调试信息传给了new。下面是这个new操作符调用的其它函数。可见是按照MFC
-& C++ -& C -& Win32
API的流程分配的内存:
-& void* __cdecl operator new(size_t nSize, LPCSTR lpszFileName, int nLine)&
afxmem.cpp
-& void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)
afxmem.cpp
-& extern "C" _CRTIMP void* __cdecl _malloc_dbg(…)&
-& extern "C" void* __cdecl _nh_malloc_dbg(…)&&&&&
-& extern "C" static void * __cdecl _nh_malloc_dbg_impl(…)&&
&dbgheap.c
-& extern "C" static void * __cdecl _heap_alloc_dbg_impl(…)&&
&dbgheap.c
-& __forceinline void * __cdecl _heap_alloc (size_t size)&&&
-& LPVOID WINAPI HeapAlloc(…);&&&
&& &winbase.h
二、内存回收操作符delete
  MFC并没有重新定义delete操作符,因为所有调试信息已经传给了new操作符。delete操作符只要依然按照MFC
-& C++ -& C -& Win32
API的流程将之前分配的内存释放掉就可以了:
operator delete
-& class CCRTAllocator::static void Free(void* p) throw()&&&
atlalloc.h
-& extern "C" _CRTIMP void __cdecl _free_dbg(void * pUserData, int nBlockUse)&
-& extern "C" void __cdecl _free_dbg_nolock(void * pUserData, int nBlockUse)&
-& void __cdecl _free_base (void * pBlock)
-& BOOL WINAPI HeapFree(…);
三、C++内存链  内存链是MFC检测内存泄露的基础,当我们每new一块内存,_heap_alloc_dbg_impl就会把这块内存加入内存链,当我们delete一块内存,_free_dbg_nolock就会把这块内存从内存链中删除。VC的实现是使用了一个双向链表。每一个节点的结构定义如下:
typedef struct _CrtMemBlockHeader
&&&&&&& struct _CrtMemBlockHeader * pBlockHeaderN&&
&// 下一个节点指针&&&&&&& struct _CrtMemBlockHeader * pBlockHeaderP&&
&// 前一个节点指针&&&&&&& char *&&&&&&&&&&&&&&&&&&&&& szFileN&&&
&// 调用new的文件名
&&&&&&& int&&&&&&&&&&&&&&&&&&&&&&&& nL&&&&
// 调用new的行号
&&&&&&& size_t&&&&&&&&&&&&&&&&&&&&& nDataS&&&
&// 调用new分配内存大小&&&&&&& int&&&&&&&&&&&&&&&&&&&&&&&& nBlockU&&&
&// 本块内存使用目的&&&&&&& long&&&&&&&&&&&&&&&&&&&&&&& lR&&&&
// 请求编号&&&&&&& unsigned char&&&&&&&&&&&&&& gap[nNoMansLandSize];&&
// 内存前面的空白&&&&&&&
} _CrtMemBlockH
  结构体中有几个成员可能需要解释一下。nBlockUse表示本块内存的用途,一般取值为_NORMAL_BLOCK。lRequest表示请求内存的编号,初始值为1,每请求一次,该值加1。我们在输出窗口看到的normal
block就表示nBlockUse=_NORMAL_BLOCK, {137}
就是lRequest的值。data是真正返回给我们的指针,编译器在data前后用gap,
anotherGap将数据保护起来并赋予特殊的值,以检测我们对指针操作是否越界。这些空白区域内存大小为#define
nNoMansLandSize 4。data同样被赋予特殊的值,特殊值总共有四种:
static unsigned char _bNoMansLandFill = 0xFD;&& static unsigned char _bAlignLandFill& = 0xED;&& static unsigned char _bDeadLandFill&& = 0xDD;&& static unsigned char _bCleanLandFill& = 0xCD;&&
比如说我们new了一个int对象,int* p = new int;那么上面这个结构体内容如下:
+------------------------------------------------------------------------------+
| pBlockHeaderNext | …… | gap: FDFDFDFD | p: CDCDCDCD | anotherGap: FDFDFDFD |
+------------------------------------------------------------------------------+
  比如说我们内存访问越界了:*(p+1) =
0,那么在delete这个指针的时候,_free_dbg_nolock会对gap,
anotherGap的值进行检查,发现不等于_bNoMansLandFill,就报错。如果我们写*(p+1) =
0xFDFDFDFD,那么就把编译器骗了,编译器认为内存访问并没有越界。当我们delete一块内存的时候,这块内存会被用_bDeadLandFill填充。如果我们new了多个对象,那么这些对象就链接再了一起,例如:
int* pB = new int;
int* pA = new int;
内存布局如下:
+--------------------------------------------------------------------------+
|&& +--------------------------+&&&&&&&&&&&&& +--------------------------+ |
+-& | pHead = pBlockHeaderNext | -----------& | pBlockHeaderNext = NULL& | |
&&& |--------------------------|&&&&&&&&&&&&& |--------------------------| |
&&& | pBlockHeaderPrev = NULL& |&&&&&&&&&&&&& | pBlockHeaderPrev&&&&& -&-|-+
&&& |--------------------------|&&&&&&&&&&&&& |--------------------------|
&&& |--------------------------|&&&&&&&&&&&&& |--------------------------|
&&& |gap: FDFDFDFD&&&&&&&&&&&& |&&&&&&&&&&&&& |gap: FDFDFDFD&&&&&&&&&&&& |
&&& |--------------------------|&&&&&&&&&&&&& |--------------------------|
&&& |pA: CDCDCDCD&&&&&&&&&&&&& |&&&&&&&&&&&&& |pB: CDCDCDCD&&&&&&&&&&&&& |
&&& |--------------------------|&&&&&&&&&&&&& |--------------------------|
&&& |anotherGap: FDFDFDFD&&&&& |&&&&&&&&&&&&& |anotherGap: FDFDFDFD&&&&& |
&&& +--------------------------+&&&&&&&&&&&&& +--------------------------+
  知道了内存块的布局,我们甚至可以通过一个指针,打印出当前new过的所有对象内存地址及大小。为了验证上述内容的正确性,我们不妨写一个简单的验证程序:
int* pB = new int(2);
int* pA = new int(1);
cout && "*pA = " && *pA && ", *pB = " && *pB &&&& // *pA = 1, *pB = 2
*((int*)(*(pA - 8)) + 8) = 1;
*((int*)(*(pB - 7)) + 8) = 2;
cout && "*pA = " && *pA && ", *pB = " && *pB &&&& // *pA = 2, *pB = 1
delete pA;
delete pB;
四、内存泄露检测机制  MFC正是因为有了内存链,才可以检测出哪些内存还没有被释放。在程序退出的时候,dbgheap.c中的extern
"C" _CRTIMP int __cdecl
_CrtDumpMemoryLeaks(void)函数会被调用,然后遍历当前的内存链,看看还有哪些内存没有被释放,然后打印出内存泄露的信息。原理很简单,这里不再赘述。那么为什么有的情况下我们无法通过输出的信息定位到具体泄露的文件呢?为什么有的时候会显示#File
看看上面提到的结构体中文件名的保存char *
szFileName,仅仅保存了一个指向文件名的指针而已。这个文件名是作为一个字符串,保存在.exe或.dll的.rdata中的。如果在.exe文件退出的时候,我们显式加载的.dll文件已经被我们卸载了,并且在该.dll文件内存存在内存泄露的话,虽然_CrtDumpMemoryLeaks会尝试读取并显示文件名,但szFileName指针指向的内存空间已经是无效的了。_CrtDumpMemoryLeaks在读取文件名之前会先调用API函数IsBadReadPtr判断该指针是否有效。如果已经无效则显示#File
Error#。本文最开始所提到的异常,正是由IsBadReadPtr导致的。
五、Release版本  对于Release版本,就没有上面提到的内存链了。对于new和delete的调用将会被直接转到malloc.c和free.c。
因为没有内存链,没有多余的保护数据填充,没有内存越界检测机制,所以有些时候Debug版本会崩溃,但是Release版本却没有。这并不代表代码没有问题,而是内存非法访问更难发现了,当Release版本崩溃的时候,问题也更难定位了。
&  上述内存泄露检测、内存越界访问检测的原理很简单,但并不能查出所有内存非法访问。所以永远不要乱用指针,然后把所有对指针的判断都用try{}catch{}规避。因为并不是所有指针非法访问都能catch到,即使catch到了,内存也可能已经被写坏了。
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。{441} client block at 0x, subtype c0, 64 bytes long.
a CDynLinkLibrary object at $ bytes long
a CDynLinkLibrary object at $ bytes long
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {440} normal block at 0x, 42 bytes long.
& 0C 00 E5 78 0C 00 00 00 0C 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {439} normal block at 0x bytes long.
& 0C 00 E5 78 0C 00 00 00 0C 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {438} normal block at 0x bytes long.
& 0C 00 E5 78 0C 00 00 00 0C 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {437} normal block at 0x, 44 bytes long.
& 0C 00 E5 78 0D 00 00 00 0D 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {436} normal block at 0x bytes long.
& 0C 00 E5 78 08 00 00 00 08 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {435} normal block at 0x bytes long.
& 0C 00 E5 78 05 00 00 00 05 00 00 00 01 00 00 00
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {434} normal block at 0x, 30 bytes long.
还有一些内存报告未贴,但都类似。查了些资料,说使用多个 MFC dll 报告内存泄漏是因为:/kb/167929/zh-cn在同一进程中加载多个版本的 MFC DLL 时,将报告这些内存泄漏。由于 MFC 扩展 (AFXDLL) dll 需要完全相同的 MFC DLL,作为调用应用程序,使用 MFC 的规则 (USRDLL) dll 或 ActiveX 控件 (OCX),使用共享的 MFC 版本时,可以只出现此问题。& 最常见的情况下混合 ANSI (MFC4xd.DLL) 和 MFC 的 UNICODE (MFC4xxUd.DLL) 版本在同一进程中。这也会发生时混合使用 MFC42d.DLL 和 MFC40d.DLL。& 这些内存泄漏通知是假,并可以被忽略。为区分内存泄露的真假,可以通过重写 CWinApp::ExitInstance() EXE 和 DLL 中的并将 TRACE() 语句放在它们来完成:C/C++ code
int CTestDllApp::ExitInstance()
TRACE(_T(&ExitInstance() for regular DLL: TESTDLL\n&));
return CWinApp::ExitInstance();
【问题】:1.在不更改各自的编码环境时,如何能不报内存泄露? &
2.我没有在SQL的DLL代码里找到int CTestDllApp::ExitInstance()这类的函数。如何区分内存泄露的真假? &
3.不同编码方式互相调用DLL是否有什么风险?望高手指点,感激不尽!祝您元旦快乐!------解决思路----------------------我的回复竟然被删了建议你去 去c++版块问问
------解决思路----------------------估计是ANSI和UNICODE在字节长度上的差异造成的越界。我只是猜测。
------解决思路----------------------
这种内存泄露是用CString类型作为参数引起的,把CString改成char*搜索“mfc\strcore.cpp(141)”
相关解决方案
Copyright &内存泄露、内存访问越界的基础认识
在堆上分配的内存,没有及时释放掉,以便后面其它地方可以重用。在中,内存管理器不会帮你自动回收不再使用的内存。如果你忘了释放不再使用的内存,这些内存就不能被重用,就造成了所谓的内存泄露。
一两处内存泄露通常不至于让程序崩溃,也不会出现逻辑上的错误,当然了,量变会产生质变,一旦内存泄露过多以致于耗尽内存,后续内存分配将会失败,程序可能因此而崩溃。
内存访问越界,使用的内存 超出了 向系统申请了一块内存,覆盖该空间之后的一段存储区域,导致系统异常。
常见原因:
1 写越界,又叫缓冲区溢出:&&&&&
向10个字节的数组写入了20个字节;内存操作越界,
szText[10];memset(szText,0,30); //访问越界了,10字节空间,修改了30字节
2 读越界,读了不属于自己的内存空间,所读地址无效,程序崩溃;地址有效,读到数据随机
排查内存越界错误的方法包括:
1.使用BoundChecker进行调试,BoundChecker在代码编译时加入了大量的附加处理,其中包括内存堆栈检测等,其实ms的
debug模式也做了许多的类似操作,但debug模式下的一些代码行为,如初始化变量,和Release下的代码执行不同,所以用debug调试不是完美的方式
2.类内部出现莫名其妙的错误时,查看 this
指针是否变化,必要的时候进行手手工检查
3.在调试的 "查看" 窗口 输入:@err,hr 看全局错误变量的内容
4.注释掉部分代码,看是否错误还出现,注释的最佳方式是:二分法
5.查看程序异常处的反汇编代码,分析原因
6.最后的办法:代码复查
引用/?uid-51504-action-viewspace-itemid-96444
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。java内存储器泄露的原因 - 编程
&&&java内存储器泄露的原因
java内存储器泄露的原因
,网友分享于: 16:18:28&&&浏览:1次
java内存泄露的原因
转载:http://blog.csdn.net/seelye/article/details/8269705
一、Java内存回收机制
在C++语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期。从申请分配、到使用、再到最后的释放。这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的泄露。Java语言对内存管理做了自己的优化,这就是垃圾回收机制。Java的几乎所有内存对象都是在堆内存上分配(基本数据类型除外),然后由GC(
collection)负责自动回收不再使用的内存。
上面是Java内存管理机制的基本情况。但是如果仅仅理解到这里,我们在实际的项目开发中仍然会遇到内存泄漏的问题。也许有人表示怀疑,既然Java的垃圾回收机制能够自动的回收内存,怎么还会出现内存泄漏的情况呢?这个问题,我们需要知道GC在什么时候回收内存对象,什么样的内存对象会被GC认为是“不再使用”的。
Java中对内存对象的访问,使用的是引用的方式。在Java代码中我们维护一个内存对象的引用变量,通过这个引用变量的值,我们可以访问到对应的内存地址中的内存对象空间。在Java程序中,这个引用变量本身既可以存放堆内存中,又可以放在代码栈的内存中(与基本数据类型相同)。GC线程会从代码栈中的引用变量开始跟踪,从而判定哪些内存是正在使用的。如果GC线程通过这种方式,无法跟踪到某一块堆内存,那么GC就认为这块内存将不再使用了(因为代码中已经无法访问这块内存了)。
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:
&一&:这些对象是可达的
&二&:这些对象是无用的
二、Java内存泄露引起原因
首先,什么是内存泄露?经常听人谈起内存泄露,但要问什么是内存泄露,没几个说得清楚。内存泄露是指无用
对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者
就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
那么,Java内存泄露根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。具体主要有如下几大类:
1、静态集合类引起内存泄露:
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
Static Vector v = new Vector(10);
for (int i = 1; i&100; i++)
Object o = new Object();
在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector
仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector
中删除,最简单的方法就是将Vector对象设置为null。
2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
public static void main(String[] args)
Set&Person& set = new HashSet&Person&();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孙悟空","pwd2",26);
Person p3 = new Person("猪八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素!
p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变
set.remove(p3); //此时remove不掉,造成内存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素!
for (Person person : set)
System.out.println(person);
在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
4、各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方
法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection
一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement
对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement
对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement
对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。
5、内部类和外部模块等的引用
内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:
public void registerMsg(Object b);
这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。
6、单例模式
不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露,考虑下面的例子:
public A(){
B.getInstance().setA(this);
//B类采用单例模式
private static B instance=new B();
public B(){}
public static B getInstance(){
public void setA(A a){
//getter...
显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况
相关解决方案
Copyright &第一资料 地图信息

我要回帖

更多关于 内存泄露 的文章

 

随机推荐