引发内存泄露了,怎么修改好像是malloc最大分配内存函数吧

1.&&&C语言的函数malloc和free
&(1)&函数malloc和free在头文件&stdlib.h&中的原型及参数
& & & &void * malloc(size_t size)
动态配置内存,大小有size决定,返回值成功时为任意类型指针,失败时为NULL。
&&&&&&&void &free(void *ptr)
释放动态申请的内存空间,调用free()后ptr所指向的内存空间被收回,如果ptr指向未知地方或者指向的空间已被收回,则会发生不可预知的错误,如果ptr为NULL,free不会有任何作用。
(2) C语言中典型用法
&&& &&& T为任意数据类型
&&&&&& T *p = ( T * )malloc( sizeof(T) * n)
&&&&&& if(NULL= =p)
&&&&&& printf(&malloc fail!\n&);
&&&&&& &&//相关资源收回的处理
&&&&&& exit(-1);
& &//此过程不能改变指针p的指向,如果改变指针指向,那么后面free将会出错,因为这里free的并不是原来的p指针,而是改变后的。当然如果改变了p的指向,那么必须再free前,将p再指回原来的位置。这样就没问题了。
注意:malloc后通常要对返回值进行判断,避免发生不必要的错误。
注意,最好再p 被free掉后,加上p=NULL这句
&野指针&不是NULL指针,是指向&垃圾&内存(不可用内存)的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是&野指针&是很危险的,if无法判断一个指针是正常指针还是&野指针&。有个良好的编程习惯是避免&野指针&的唯一方法。
指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是&垃圾&内存。释放后的指针应立即将指针置为NULL,防止产生&野指针&。
(3)&内存说明
malloc函数动态申请的内存空间是在堆里(而一般局部变量存于栈里),并且该段内存不会被初始化,与全局变量不一样,如果不采用手动free()加以释放,则该段内存一直存在,直到程序退出才被系统,所以为了合理使用内存,在不适用该段内存时,应该调用free()。另外,如果在一个函数里面使用过malloc,最好要配对使用free,否则容易造成内存泄露(没有将内存还给自由存储区)。
但是,往往会在free的时候发生段错误.
正确的做法是这样:
//&在分配之前加一句判断指针是否为空,防止产生内存泄露
struct XXXX * ptr=NULL;if&(ptr&==&NULL)&{ptr&&=&&(struct&XXXX&*)malloc(num&*&sizeof(struct&XXXX);}//&在释放之前加一句判断指针是否为空,防止产生异常if&(ptr&!=&NULL)&{free(ptr);ptr&=&NULL;}
补充:C&语言作为&Linux&系统上标准的编程语言给予了我们对动态内存分配很大的控制权。
然而,这种自由可能会导致严重的内存管理问题,而这些问题可能导致程序崩溃或随时间的推移导致性能降级。内存泄漏(即&malloc()&内存在对应的&free()&调用执行后永不被释放)和缓冲区溢出(例如对以前分配到某数组的内存进行写操作)是一些常见的问题,它们可能很难检测到。这一部分将讨论几个调试工具,它们极大地简化了检测和找出内存问题的过程。只要在代码中添加一个头文件并在&gcc&语句中定义了&MEMWATCH&之后,您就可以跟踪程序中的内存泄漏和错误了。MEMWATCH&支持&ANSI&C,它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneous&free)、没有释放的内存(unfreed&memory)、溢出和下溢等等。
补充2:转自:/yfanqiu/archive//2490410.html
2.&&C++中的运算符new和delete
new和delete是C++中的运算符,不是库函数,不需要库的支持,同时,他们是封装好的运算符。
(1)new是动态分配内存的运算符,自动计算需要分配的空间,在分配类类型的内存空间时,同时调用类的构造函数,对内存空间进行初始化,即完成类的初始化工作。动态分配内置类型是否自动初始化取决于变量定义的位置,在函数体外定义的变量都初始化为0,在函数体内定义的内置类型变量都不进行初始化。
(2)delete是撤销动态申请的内存运算符。delete与new通常配对使用,与new的功能相反,可以对多种数据类型形式的内存进行撤销,包括类,撤销类的内存空间时,它要调用其析构函数,完成相应的清理工作,收回相应的内存资源。
(3)典型用法
int *p = new int;&&&&&&&& &&&&&&&&&&&&& delete p;
char *p = new char;&&& &&&&&&&&&&&&& delete p;
类的类型&*p = new&类的类型;&delete p;
//注意,指针p存于栈中,p所指向的内存空间却是在堆中。
&&&&&&&&&&&&&&&&&&&&&&&&&&& Obj * p = new Obj[100];&&&&&&&&&&&&&&&&&&&& delete [ ]p;
//注意,new申请数组,delete删除的形式需要加括号&[ ]&,表示对数组空间的操作,总之,申请形式如何,释放的形式就如何。
(4)内存说明。new申请的内存也是存于堆中,所以在不需要使用时,需要delete手动收回。
3.&&new/delete与malloc/free之间的联系和区别
(1)&&&&&&&&& malloc/free和new/delete的联系
a)存储方式相同。malloc和new动态申请的内存都位于堆中。申请的内存都不能自动被操作系统收回,都需要配套的free和delete来释放。
b)除了带有构造函数和析构函数的类等数据类型以外,对于一般数据类型,如int、char等等,两组动态申请的方式可以通用,作用效果一样,只是形式不一样。
c)内存泄漏对于malloc或者new都可以检查出来的,区别在于new可以指明是那个文件的那一行,而malloc没有这些信息。
d)两组都需要配对使用,malloc配free,new配delete,注意,这不仅仅是习惯问题,如果不配对使用,容易造成内存泄露。同时,在C++中,两组之间不能混着用,虽说有时能编译过,但容易存在较大的隐患。
(2)&&&&&&&&& malloc/free和new/delete的区别
a)malloc和free返回void类型指针,new和delete直接带具体类型的指针。
b)malloc和free属于C语言中的函数,需要库的支持,而new/delete是C++中的运算符,所以new/delete的执行效率高些。C++中为了兼用C语法,所以保留malloc和free的使用,但建议尽量使用new和delete。
c)在C++中,&new是类型安全的,而malloc不是。例如:
int* p = new char[10]; &&&&&&&&&&&&&&&&&& //&编译时指出错误
& delete [ ]p;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //对数组需要加中括号&[ ]&
int* p = malloc(sizeof(char )*10); && //&编译时无法指出错误
&& free (p);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //只需要所释放内存的头指针
d)使用new动态申请类对象的内存空间时,类对象的构建要调用构造函数,相当于对内存空间进行了初始化。而malloc动态申请的类对象的内存空间时,不会初始化,也就是说申请的内存空间无法使用,因为类的初始化是由构造函数完成的。delete和free的意义分别于new和malloc相反。
e)不能用malloc和free来完成类对象的动态创建和删除。
4.&&C/C++程序的内存分配介绍
该部分参考于
(1)栈内存分配运算内置于处理器的指令集中,一般使用寄存器来存取,效率很高,但是分配的内存容量有限。一般局部变量和函数参数的暂时存放位置。
(2)堆内存,亦称动态内存。如malloc和new申请的内存空间。动态内存的生存期由程序员自己决定,使用非常灵活。
(3)全局代码区:从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(4)常量区:文字常量分配在文字常量区,程序结束后由系统释放。
(5)代码区:存放整个程序的代码,因为存储是数据和代码分开存储的。
(1)new、delete 是操作符,只能在C++中使用。malloc、free是函数,可以覆盖,C、C++中都可以使用。
(2)new 自动计算需要分配的空间大小,可以调用对象的构造函数,对应的delete调用相应的析构函数。malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数
(3)new 类型安全、返回的是某种数据类型指针,malloc 非类型安全、返回的是void指针。
阅读(...) 评论()C语言中什么叫内存泄露?就是申请空间后没有及时注销,导致课分配内存越来越小。 内存泄露 就是你用malloc之类函数在堆 内存动态申请了空间,但是使用完以后没有拿free函数释放~这样 内存就得不到回收~就叫内存泄露~ 你的指针空间没释放出来,这个C Primer Plus上有吧 申请了内存,而没有释放内存。
如执行了如int *p=(int *)malloc(sizeof(int));的语句,但没有在程序末尾用free(p)释放内存,申请的内存就会一直处于被占用状态,就是内存泄露。
谢谢采纳! 内存泄漏通常是指分配出去的内存使用完毕后没有释放掉,未被收回。长此以往,当再要求分配内存时,没有足够的内存可以分配,就会报出内存泄漏(memory leak)。 在内存中申请空间,但是在使用后没有销毁,这个空间的引用不在了,但是未被释放,会对安全性造成威胁 比如说你的是int a[5],但是你在后面又写的是啊a[5]=2,这什么叫内存泄漏个时候就泄露了,
因为你申请的是5个int型的空间,但a[5]其实事第6个了!
a[0],a[1],a[2],a[3],a[4]! 应用程序的内存分为四块:堆区,栈区,全局数据区,代码区
其中堆区需要程序员自己管理,我们申请的动态变量就存放在堆区,用完后需要程序员自己手动释放,若申请了一块动态内存,未释放,却丢失了指向性,这就叫内存泄露 会导致程序的可用内存减少,严重的话会拖垮操作系统 就是申请空间后没有及时注销,导致课分配内存越来越小。 内存泄露 就是你用malloc之类函数在堆 内存动态申请了空间,但是使用完以后没有拿free函数释放~这样 内存就得不到回收~就叫内存泄露~ 你的指针空间没释放出来,这个C Primer Plus上有吧 申请了内存,而没有释放内存。
如执行了如int *p=(int *)malloc(sizeof(int));的语句,但没有在程序末尾用free(p)释放内存,申请的内存就会一直处于被占什么叫内存泄漏用状态,就是内存泄露。
谢谢采纳! 内存泄漏通常是指分配出去的内存使用完毕后没有释放掉,未被收回。长此以往,当再要求分配内存时,没有足够的内存可以分配,就会报出内存泄漏(memory leak)。 在内存中申请空间,但是在使用后没有销毁,这个空间的引用不在了,但是未被释放,会对安全性造成威胁 比如说你的是int a[5],但是你在后面又写的是啊a[5]=2,这个时候就泄露了,
因为你申请的是5个int型的空间,但a[5]其实事第6个了!
a[0],a[1],a[2],a[3],a[4]! 应用程序的内存分为四块:堆区,栈区,全局数据区,代码区
其中堆区需要程序员自己管理,我们申请的动态变量就存放在堆区,用完后需要程序员自己手动释放,若申请了一块动态内存,未释放,却丢失了指向性,这就叫内存泄露 会导致程序的可用内存减少,严重的话会拖垮操作系统
访问统计:浅谈C/C++内存泄露及其检测工具
浅谈C/C++内存泄露及其检测工具
  于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题。已经有许多技术被研究出来以应对这个问题,比如 Smart Pointer,Garbage Collection等。Smart Pointer技术比较成熟,STL中已经包含支持Smart Pointer的class,但是它的使用似乎并不广泛,而且它也不能解决所有的问题;Garbage Collection技术在Java中已经比较成熟,但是在c/c++领域的发展并不顺畅,虽然很早就有人思考在C++中也加入GC的支持。现实世界就是这样的,作为一个c/c++程序员,内存泄漏是你心中永远的痛。不过好在现在有许多工具能够帮助我们验证内存泄漏的存在,找出发生问题的代码。
  内存泄漏的定义
  一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形:
&void MyFunction(int nSize){ char* p= new char[nSize]; if( !GetStringFrom( p, nSize ) ){  MessageBox(“Error”);   } …//using the }
  当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,但是c函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。
  广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。
  GDI Object的泄漏是一种常见的资源泄漏:
&void CMyView::OnPaint( CDC* pDC ){ CB CBitmap* pOldB bmp.LoadBitmap(IDB_MYBMP); pOldBmp = pDC-&SelectObject( &bmp ); … if( Something() ){   } pDC-&SelectObject( pOldBmp ); }
  当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很多。
  内存泄漏的发生方式:
  以发生的方式来分类,内存泄漏可以分为4类:
  1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。
  2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回 True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
  3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:
  char* g_lpszFileName = NULL;
  void SetFileName( const char* lpcszFileName ){ if( g_lpszFileName ){  free( g_lpszFileName ); } g_lpszFileName = strdup( lpcszFileName );}
  如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。
  4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一个例子:
  class Connection{ public:  Connection( SOCKET s);  ~Connection();  … private:  SOCKET _  …};
  class ConnectionManager{ public:  ConnectionManager(){}  ~ConnectionManager(){   list::   for( it = _connlist.begin(); it != _connlist.end(); ++it ){    delete (*it);   }   _connlist.clear();  }  void OnClientConnected( SOCKET s ){   Connection* p = new Connection(s);   _connlist.push_back(p);  }  void OnClientDisconnected( Connection* pconn ){   _connlist.remove( pconn );     } private:  list _};
  假设在Client从Server端断开后,Server并没有呼叫OnClientDisconnected()函数,那么代表那次连接的 Connection对象就不会被及时的删除(在Server程序退出的时候,所有Connection对象会在ConnectionManager的析构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。
  从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
  检测内存泄漏
  检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,我们就能跟踪每一块内存的生命周期,比如,每当成功的分配一块内存后,就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这样,当程序结束的时候,list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理,详细的算法可以参见Steve Maguire的&&Writing Solid Code&&.
  如果要检测堆内存的泄漏,那么需要截获住 malloc/realloc/free和new/delete就可以了(其实new/delete最终也是用malloc/free的,所以只要截获前面一组即可)。对于其他的泄漏,可以采用类似的方法,截获住相应的分配和释放函数。比如,要检测BSTR的泄漏,就需要截获 SysAllocString/SysFreeString;要检测HMENU的泄漏,就需要截获CreateMenu/ DestroyMenu.(有的资源的分配函数有多个,释放函数只有一个,比如,SysAllocStringLen也可以用来分配BSTR,这时就需要截获多个分配函数)
  在Windows平台下,检测内存泄漏的工具常用的一般有三种,MS C-Runtime Library内建的检测功能;外挂式的检测工具,诸如,Purify,BoundsChecker等;利用Windows NT自带的Performance Monitor.这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工具要弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。
  以下我们详细讨论这三种检测工具:
  VC下内存泄漏的检测方法
  用MFC开发的应用程序,在DEBUG版模式下编译后,都会自动加入内存泄漏的检测代码。在程序结束后,如果发生了内存泄漏,在Debug窗口中会显示出所有发生泄漏的内存块的信息,以下两行显示了一块被泄漏的内存块的信息:
  E:\TestMemLeak\TestDlg.cpp(70) : {59} normal block at 0x, 200 bytes long.
  Data: &abcdefghijklmnop& 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70
  第一行显示该内存块由TestDlg.cpp文件,第70行代码分配,地址在0x,大小为200字节,{59}是指调用内存分配函数的 Request Order,关于它的详细信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行显示该内存块前16个字节的内容,尖括号内是以 ASCII方式显示,接着的是以16进制方式显示。
  一般大家都误以为这些内存泄漏的检测功能是由MFC提供的,其实不然。MFC只是 封装和利用了MS C-Runtime Library的Debug Function.非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free,strdup等函数时已经内建了内存泄漏的检测功能。
  注意观察一下由MFC Application Wizard生成的项目,在每一个cpp文件的头部都有这样一段宏定义:
&#ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif
  有了这样的定义,在编译DEBUG版时,出现在这个cpp文件中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢?DEBUG_NEW也是一个宏,以下摘自afx.h,1632行
  #define DEBUG_NEW new(THIS_FILE, __LINE__)
  所以如果有这样一行代码:
  char* p = new char[200];
  经过宏替换就变成了:
  char* p = new( THIS_FILE, __LINE__)char[200];
  根据C++的标准,对于以上的new的使用方法,编译器会去找这样定义的operator new:
  void* operator new(size_t, LPCSTR, int)
  我们在afxmem.cpp 63行找到了一个这样的operator new 的实现
  void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine){ return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);}
  void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine){ … pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine); if (pResult != NULL)  return pR …
  第二个operator new函数比较长,为了简单期间,我只摘录了部分。很显然最后的内存分配还是通过_malloc_dbg函数实现的,这个函数属于MS C-Runtime Library 的Debug Function.这个函数不但要求传入内存的大小,另外还有文件名和行号两个参数。文件名和行号就是用来记录此次分配是由哪一段代码造成的。如果这块内存在程序结束之前没有被释放,那么这些信息就会输出到Debug窗口里。
  这里顺便提一下THIS_FILE,__FILE和 __LINE__.__FILE__和__LINE__都是编译器定义的宏。当碰到__FILE__时,编译器会把__FILE__替换成一个字符串,这个字符串就是当前在编译的文件的路径名。当碰到__LINE__时,编译器会把__LINE__替换成一个数字,这个数字就是当前这行代码的行号。在 DEBUG_NEW的定义中没有直接使用__FILE__,而是用了THIS_FILE,其目的是为了减小目标文件的大小。假设在某个cpp文件中有 100处使用了new,如果直接使用__FILE__,那编译器会产生100个常量字符串,这100个字符串都是飧?/SPAN&cpp文件的路径名,显然十分冗余。如果使用THIS_FILE,编译器只会产生一个常量字符串,那100处new的调用使用的都是指向常量字符串的指针。
  再次观察一下由MFC Application Wizard生成的项目,我们会发现在cpp文件中只对new做了映射,如果你在程序中直接使用malloc函数分配内存,调用malloc的文件名和行号是不会被记录下来的。如果这块内存发生了泄漏,MS C-Runtime Library仍然能检测到,但是当输出这块内存块的信息,不会包含分配它的的文件名和行号。
  要在非MFC程序中打开内存泄漏的检测功能非常容易,你只要在程序的入口处加入以下几行代码:
  int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
  tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
  _CrtSetDbgFlag( tmpFlag );
  这样,在程序结束的时候,也就是winmain,main或dllmain函数返回之后,如果还有内存块没有释放,它们的信息会被打印到Debug窗口里。
  如果你试着创建了一个非MFC应用程序,而且在程序的入口处加入了以上代码,并且故意在程序中不释放某些内存块,你会在Debug窗口里看到以下的信息:
  {47} normal block at 0x00C91C90, 200 bytes long.
  Data: & & 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
  内存泄漏的确检测到了,但是和上面MFC程序的例子相比,缺少了文件名和行号。对于一个比较大的程序,没有这些信息,解决问题将变得十分困难。
  为了能够知道泄漏的内存块是在哪里分配的,你需要实现类似MFC的映射功能,把new,maolloc等函数映射到_malloc_dbg函数上。这里我不再赘述,你可以参考MFC的源代码。
  由于Debug Function实现在MS C-RuntimeLibrary中,所以它只能检测到堆内存的泄漏,而且只限于malloc,realloc或strdup等分配的内存,而那些系统资源,比如HANDLE,GDI Object,或是不通过C-Runtime Library分配的内存,比如VARIANT,BSTR的泄漏,它是无法检测到的,这是这种检测法的一个重大的局限性。另外,为了能记录内存块是在哪里分配的,源代码必须相应的配合,这在调试一些老的程序非常麻烦,毕竟修改源代码不是一件省心的事,这是这种检测法的另一个局限性。
  对 于开发一个大型的程序,MS C-Runtime Library提供的检测功能是远远不够的。接下来我们就看看外挂式的检测工具。我用的比较多的是BoundsChecker,一则因为它的功能比较全面,更重要的是它的稳定性。这类工具如果不稳定,反而会忙里添乱。到底是出自鼎鼎大名的NuMega,我用下来基本上没有什么大问题。
  使用BoundsChecker检测内存泄漏:
  BoundsChecker采用一种被称为 Code Injection的技术,来截获对分配内存和释放内存的函数的调用。简单地说,当你的程序开始运行时,BoundsChecker的DLL被自动载入进程的地址空间(这可以通过system-level的Hook实现),然后它会修改进程中对内存分配和释放的函数调用,让这些调用首先转入它的代码,然后再执行原来的代码。BoundsChecker在做这些动作的时,无须修改被调试程序的源代码或工程配置文件,这使得使用它非常的简便、直接。
  这里我们以malloc函数为例,截获其他的函数方法与此类似。
  需要被截获的函数可能在DLL中,也可能在程序的代码里。比如,如果静态连结C-Runtime Library,那么malloc函数的代码会被连结到程序里。为了截获住对这类函数的调用,BoundsChecker会动态修改这些函数的指令。
  以下两段汇编代码,一段没有BoundsChecker介入,另一段则有BoundsChecker的介入:
  126: _CRTIMP void * __cdecl malloc (127: size_t nSize128: )129: {
  00403C10 push ebp00403C11 mov ebp,esp130: return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);00403C13 push 000403C15 push 000403C17 push 100403C19 mov eax,[__newmode (0042376c)]00403C1E push eax00403C1F mov ecx,dword ptr [nSize]00403C22 push ecx00403C23 call _nh_malloc_dbg (00403c80)00403C28 add esp,14h131: }&&& 以下这一段代码有BoundsChecker介入:
  126: _CRTIMP void * __cdecl malloc (127: size_t nSize128: )129: {
  00403C10 jmp 01F41EC800403C15 push 000403C17 push 100403C19 mov eax,[__newmode (0042376c)]00403C1E push eax00403C1F mov ecx,dword ptr [nSize]00403C22 push ecx00403C23 call _nh_malloc_dbg (00403c80)00403C28 add esp,14h131: }
  当BoundsChecker介入后,函数malloc的前三条汇编指令被替换成一条jmp指令,原来的三条指令被搬到地址01F41EC8处了。当程序进入malloc后先jmp到01F41EC8,执行原来的三条指令,然后就是BoundsChecker的天下了。大致上它会先记录函数的返回地址(函数的返回地址在stack上,所以很容易修改),然后把返回地址指向属于BoundsChecker的代码,接着跳到malloc函数原来的指令,也就是在00403c15的地方。当malloc函数结束的时候,由于返回地址被修改,它会返回到BoundsChecker的代码中,此时 BoundsChecker会记录由malloc分配的内存的指针,然后再跳转到到原来的返回地址去。
  如果内存分配/释放函数在DLL中,BoundsChecker则采用另一种方法来截获对这些函数的调用。BoundsChecker通过修改程序的DLL Import Table让table中的函数地址指向自己的地址,以达到截获的目的。
  截获住这些分配和释放函数,BoundsChecker就能记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关,也就是说当 BoundsChecker检测到内存泄漏,它如何报告这块内存块是哪段代码分配的。答案是调试信息(Debug Information)。当我们编译一个Debug版的程序时,编译器会把源代码和二进制代码之间的对应关系记录下来,放到一个单独的文件里 (。pdb)或者直接连结进目标程序,通过直接读取调试信息就能得到分配某块内存的源代码在哪个文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能记录呼叫分配函数的源代码的位置,而且还能记录分配时的Call Stack,以及Call Stack上的函数的源代码位置。这在使用像MFC这样的类库时非常有用,以下我用一个例子来说明:
  void ShowXItemMenu(){ … CM
  menu.CreatePopupMenu(); //add menu items. menu.TrackPropupMenu(); …}
  void ShowYItemMenu( ){ … CM menu.CreatePopupMenu(); //add menu items. menu.TrackPropupMenu(); menu.Detach();//this will cause HMENU leak …}
  BOOL CMenu::CreatePopupMenu(){ … hMenu = CreatePopupMenu(); …}
&&& 当调用ShowYItemMenu()时,我们故意造成HMENU的泄漏。但是,对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假设的你的程序有许多地方使用了CMenu的CreatePopupMenu()函数,如 CMenu::CreatePopupMenu()造成的,你依然无法确认问题的根结到底在哪里,在ShowXItemMenu()中还是在 ShowYItemMenu()中,或者还有其它的地方也使用了CreatePopupMenu()?有了Call Stack的信息,问题就容易了。BoundsChecker会如下报告泄漏的HMENU的信息:
  FunctionFileLine
  CMenu::CreatePopupMenuE:\8168\vc98\mfc\mfc\include\afxwin1.inl1009
  ShowYItemMenuE:\testmemleak\mytest.cpp100
  这里省略了其他的函数调用
  如此,我们很容易找到发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编程时,大部分的API调用都被封装在类库的class里,有了Call Stack信息,我们就可以非常容易的追踪到真正发生泄漏的代码。
  记录Call Stack信息会使程序的运行变得非常慢,因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开关:
  1. 打开菜单:BoundsChecker|Setting…
  2. 在Error Detection页中,在Error Detection Scheme的List中选择Custom
  3. 在Category的Combox中选择 Pointer and leak error check
  4. 钩上Report Call Stack复选框
  5. 点击Ok
  基于Code Injection,BoundsChecker还提供了API Parameter的校验功能,memory over run等功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题,所以不在此详述了。
  尽管BoundsChecker的功能如此强大,但是面对隐式内存泄漏仍然显得苍白无力。所以接下来我们看看如何用Performance Monitor检测内存泄漏。
  使用Performance Monitor检测内存泄漏
  NT的内核在设计过程中已经加入了系统监视功能,比如CPU的使用率,内存的使用情况,I/O操作的频繁度等都作为一个个Counter,应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。
  为了检测内存泄漏,我们一般可以监视Process对象的Handle Count,Virutal Bytes 和Working Set三个Counter.Handle Count记录了进程当前打开的HANDLE的个数,监视这个Counter有助于我们发现程序是否有Handle泄漏;Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小,NT的内存分配采用了两步走的方法,首先,在虚地址空间上保留一段空间,这时操作系统并没有分配物理内存,只是保留了一段地址。然后,再提交这段空间,这时操作系统才会分配物理内存。所以,Virtual Bytes一般总大于程序的Working Set.监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量,这个值和程序申请的内存总量存在密切的关系,如果程序存在内存的泄漏这个值会持续增加,但是 Virtual Bytes却是跳跃式增加的。
  监视这些Counter可以让我们了解进程使用内存的情况,如果发生了泄漏,即使是隐式内存泄漏,这些Counter的值也会持续增加。但是,我们知道有问题却不知道哪里有问题,所以一般使用Performance Monitor来验证是否有内存泄漏,而使用BoundsChecker来找到和解决。
  当Performance Monitor显示有内存泄漏,而BoundsChecker却无法检测到,这时有两种可能:第一种,发生了偶发性内存泄漏。这时你要确保使用 Performance Monitor和使用BoundsChecker时,程序的运行环境和操作方法是一致的。第二种,发生了隐式的内存泄漏。这时你要重新审查程序的设计,然后仔细研究Performance Monitor记录的Counter的值的变化图,分析其中的变化和程序运行逻辑的关系,找到一些可能的原因。这是一个痛苦的过程,充满了假设、猜想、验证、失败,但这也是一个积累经验的绝好机会。
H3C认证Java认证Oracle认证
基础英语软考英语项目管理英语职场英语
.NETPowerBuilderWeb开发游戏开发Perl
二级模拟试题一级模拟试题一级考试经验四级考试资料
软件测试软件外包系统分析与建模敏捷开发
法律法规历年试题软考英语网络管理员系统架构设计师信息系统监理师
高级通信工程师考试大纲设备环境综合能力
路由技术网络存储无线网络网络设备
CPMP考试prince2认证项目范围管理项目配置管理项目管理案例项目经理项目干系人管理
职称考试题目
招生信息考研政治
网络安全安全设置工具使用手机安全
生物识别传感器物联网传输层物联网前沿技术物联网案例分析
Java核心技术J2ME教程
Linux系统管理Linux编程Linux安全AIX教程
Windows系统管理Windows教程Windows网络管理Windows故障
数据库开发Sybase数据库Informix数据库
&&&&&&&&&&&&&&&
希赛网 版权所有 & &&}

我要回帖

更多关于 malloc分配内存 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信