⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 nei.txt

📁 利用人工智能的经典算法实现迷宫游戏;里面的A星(a*)算法可以很方便的移植到应用程序中
💻 TXT
字号:
就是创建了之后没有释放   
  如调用了p   =   malloc(SIZE),使用完后应该调用free(p);将其释放,否则就产生了内存泄漏,即这块内存没有程序使用,也不能进行再分配,使系统可用的内存逐渐减少,严重的可能导致资源耗尽。

用MFC开发的应用程序,在DEBUG版模式下编译后,都会自动加入内存泄漏的检测代码。
在程序结束后,如果发生了内存泄漏,在Debug窗口中会显示出所有发生泄漏的内存块的信息,
以下两行显示了一块被泄漏的内存块的信息:

E:\TestMemLeak\TestDlg.cpp(70) : {59} normal block at 0x00881710, 200 bytes long.
Data: <abcdefghijklmnop> 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70

第一行显示该内存块由TestDlg.cpp文件,第70行代码分配,地址在0x00881710,大小为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_FILE

static 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:
//( THIS_FILE, __LINE__)称为 new(delete也一样)的定位实参, new 的第一个参数类型必须是 size_t,额外的参数由 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 pResult;
}


第二个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 );      // 取得当前的 _crtDbgFlag 状态值并存储在 tmpFlag 中


tmpFlag |= _CRTDBG_LEAK_CHECK_DF;                         // 修改 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的源代码。

/* 在文件CRTDBG.H中存在一系列 macro 定义如下:
#ifdef     _CRTDBG_MAP_ALLOC

#define    malloc(s)         _malloc_dbg(s, _NORMAL_BLOCK_, __FILE__, __LINE__)
#define    calloc(c, s)      _calloc_dbg(c, s, _NORMAL_BLOCK, __FILE__, __LINE__)
#define    realloc(p, s)     _realloc_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__)
#define    _expand(p, s)     _expand_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__)
#define    free(p)           _free_dbg(p, _NORMAL_BLOCK)
#define    _msize(p)         _msize_dbg(p, _NORMAL_BLOCK)

#endif     /* _CRTDBG_MAP_ALLOC */
   这几个宏定义就实现了映射的功能,你只要在你的程序中加入定义 
#define    _CRTDBG_MAP_ALLOC   
   就行了.当然也可以把这几个宏定义语句直接拷贝到程序中,这样更明了.
*/


由于Debug Function实现在MS C-RuntimeLibrary中,所以它只能检测到堆内存的泄漏,而且只限于malloc,realloc或strdup等分配的内存,而那些系统资源,比如HANDLE,GDI Object,或是不通过C-Runtime Library分配的内存,比如VARIANT,BSTR的泄漏,它是无法检测到的,这是这种检测法的一个重大的局限性。另外,为了能记录内存块是在哪里分配的,源代码必须相应的配合,这在调试一些老的程序非常麻烦,毕竟修改源代码不是一件省心的事,这是这种检测法的另一个局限性。


/*注 :  一些常用的 MS C-Runtime Library 的Debug Function 函数

<1> : _CrtDumpMemoryLeaks()      //在vc中常见的内存泄漏信息就是由这个函数以用户可度的形式输出的,通常 _CrtDumpMemoryLeaks 会 dump 内存泄漏信息到 output 的 debug 栏.(可以使用 _CrtSetReportMode() 来重新设置输出到另外的地方)


<2> : _CrtSetReportMode(int reportType, int reportMode )        //设置report 方式的函数,(内存泄漏信息是属于reportType 中的 _CRT_WARN 类型)
      _CrtSetReportFile()                                       //和上一个函数连用,指定输出的文件

比如调用 :

_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );

 _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT ); 

可以将内存内存泄漏的信息输出到标准输出


      
 
<3> : _CrtSetBreakAlloc()        //设置内存分配断点,从output信息中得到内存分配的分配块号作为参数调用就行了,但要注意两次运行程序应保持一致,否则同一条分配语句可能分配的内存的块号是不同的,那这个断点就丢失了


<4> : _CrtMemCheckpoint( _CrtMemState *state )                  // 设置内存状态的检查点,内存状态将被填充到参数的结构中


<5> : _CrtMemDumpStatistics( _CrtMemState *state )              // 可以 dump 由上一个函数得到的堆内存状态信息


<6> : _CrtMemDifference( _CrtMemState *stateDiff, const _CrtMemState *oldState, const _CrtMemState *newState )          //比较 oldState 和 newState 之间的差别,差异的结果存放在 stateDiff 中

当然这些函数的使用都必须是在 _DEBUG 已定义的情况下,否则这些 MS C-Runtime Library 的 debug functions 的调用会在预编译过程中被删除

*/
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了

void   f1(void)   
  {   
        int   *   p;     
        p   =   new   int;   
  }   
    
  执行了F1函数后,p所分配到的内存地址就丢了。找不回那个int内存了,所以这里的内存泄露了!一般多是指内存分配未回收。
Top

2 楼wlz47(秋水沉舟)回复于 2002-04-13 10:01:36 得分 0 打个比方:内存就象一个旅馆,旅馆的每个房子都有编号并记录在案   
  客人来了,管理者根据这些编号的记录给客人分配房间,现在某个编号不小心弄丢了,不再被记录在案了,那么这个房间对于管理者来说就相当于不存在了,我们说这就发生了“房间泄漏”   
    
  再看J_John同志的例子,当调用函数f1()时,已分配了一块内存,并给了编号p,   
  现在函数调用结束了,这个编号却没有传出去,于是就发生内存泄漏   
  
Top

3 楼jishiping(JSP 季世平)回复于 2002-04-13 10:04:41 得分 0 1.用   malloc   分配了,但没有用   free   释放。   
  2.用   new   分配了,但没有用   delete   删除。   
  3.用   GlobalAlloc   分配,但没有用   GlobalFree   释放。   
  4.用   new   []   分配的数组,没有用   delete[]   删除,而是用   delete   删除。   
  如:   
      struct   A   {   
            int   num;   
      }   
    
      struct   A*   pa;   
      pa   =   new   A[10];   
      delete   pa;   //应该用delete[]   pa;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -