内存泄漏检查
发布:siney | 发布时间: 2006 04 04这几天一直在查游戏的memory leak问题,查这个问题真是头疼,应为引擎设计的问题(原因见我上篇BLOG),检查问题异常困难,虽然至今这个问题还没有有效的解决方法,但总算有些心得,写出来同大家分享一下。
为了能分析真正意义上的memory leak,需要把所有new的singleton在程序关闭的时候delete掉,目前是new了不delete的,因为是singleton,所以这样的不会导致多次new,程序只有一份singleton实例,在process关闭后会回收这些内存,这样的设计有好有怀,我现在发现的好处是程序结构会简单很多;对于某些需要内存对齐的static singleton不需要new(因为new了不能保证对齐),坏处就是检查memory leak异常困难;如果存在依赖关系,不好控制析构顺序。依我的个人观点来看,这样的设计是弊大于利,不过没有办法,事已至此总要想些办法弥补。
为了能解决new 产生的singleton,需要在某个地方集中delete掉,而这个地方又必须是所有全局static类已经析构后的一个地方,这样我们可以同样创建static的全局singleton类(假设命名为gcFinalizor),保证这个类是最开始被构造,最后被析构,在vc编译器下,只要把
#pragma init_seg(lib) 或者 #pragma init_seg(".CRT$XCA")
放在gcFinalizor类的cpp文件内,并使用static产生该类的singleton实体,这样vc编译器可以保证这个gcFinalizor一定是在其他static类之前被初始化,在所有static类析构后再析构,这样我们在gcFinalizor类的析构函数内释放掉所有收集到的其他new的singleton,为此只需要在其他new的singleton除插入一行收集代码就可以。为了还能解决析构时控制循序,还需要在收集的时候设定一个优先级,最后根据优先级排序,这样new的singleton问题就解决了。
接下来需要使用CRT函数来分析内存泄漏,简单的方法就是在debug模式,程序结束的地方(gcFinalizor的析构函数内)调用_CRTDumpMemoryLeaks函数来dump所有分配了未被释放的内存块,但我发现简单使用这个函数会dump出来很多,尤其是在还没有解决new的singleton问题时。为此可以使用_CrtSetDbgFlag来标记某些你确信没有内存泄漏的代码,就像这样:
_CrtSetDbgFlag(0);
...
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF);
在_CrtSetDbgFlag之间的代码是不会被dump的,就算存在分配了没有释放,这需要是标定某些确信的singleton内存块,这样可以减少dump出来的信息,集中精力分析可疑代码。
还有可以使用_CrtMemCheckpoint函数来做内存状态快照,比如进入某个函数之前_CrtMemCheckpoint一次,退出后_CrtMemCheckpoint一次,然后通过_CrtMemDifference来比较前后两次内存状态的差别,最后使用_CrtMemDumpStatistics输出差别信息,在实际中,我发现这个功能很好,稍微做一下封装,使用其检查内存泄漏很方便。最后还有一个实用的函数就是_CrtMemDumpAllObjectsSince,它的作用就是dump出自某次内存快照之后的未释放内存块,如果直接传入参数NULL,其功能等同与_CRTDumpMemoryLeaks。
最后说说发现了内存泄漏信息,如何定位代码吧,通过_CRTDumpMemoryLeaks dump的可以内存泄漏信息一般是:
{xxx} NORMAL .............. zzz bytes ...........
我们只需要关心xxx和zzz就可以了,xxx表示了内存分配id,zzz表示了分配大小,通过xxx我们确定是第几次分配内存时产生未释放的问题(这依赖与你的操作,这里我们假定操作完全相同,那么xxx在多次程序运行过程是一定的,如果操作不同xxx则不完全相同,此时这个xxx的意义不大,但我们通过录制操作过程来回放保证操作相同;如果是多线程程序,而这次内存分配又是在某个线程内完成的,那么这个xxx基本没有意义),我们可以通过调用函数_CrtSetBreakAlloc(xxx)来断定定位改次内存,然后察看call stack判定代码位置,为了能不每次检查不同的xxx而重新编译代码,把xxx放在一个文件中,程序启动的时候读取这个xxx,然后调用_CrtSetBreakAlloc函数来下断点是非常重要的,要不link一个程序的时间可能等很久。
最后最为重要的是,记得要在你所有的cpp文件中加入
#include
#include //如果使用malloc,free函数,必须这样的include顺序
保证的内存分配是使用了可debug的_malloc_dbg函数,这样上面说那些函数才能正常工作,为了简化问题,你可以把它放在pch.hpp这类precompiler header文件文件中,一般我们工程第一个include的文件都应该是pch.hpp文件。
为了能分析真正意义上的memory leak,需要把所有new的singleton在程序关闭的时候delete掉,目前是new了不delete的,因为是singleton,所以这样的不会导致多次new,程序只有一份singleton实例,在process关闭后会回收这些内存,这样的设计有好有怀,我现在发现的好处是程序结构会简单很多;对于某些需要内存对齐的static singleton不需要new(因为new了不能保证对齐),坏处就是检查memory leak异常困难;如果存在依赖关系,不好控制析构顺序。依我的个人观点来看,这样的设计是弊大于利,不过没有办法,事已至此总要想些办法弥补。
为了能解决new 产生的singleton,需要在某个地方集中delete掉,而这个地方又必须是所有全局static类已经析构后的一个地方,这样我们可以同样创建static的全局singleton类(假设命名为gcFinalizor),保证这个类是最开始被构造,最后被析构,在vc编译器下,只要把
#pragma init_seg(lib) 或者 #pragma init_seg(".CRT$XCA")
放在gcFinalizor类的cpp文件内,并使用static产生该类的singleton实体,这样vc编译器可以保证这个gcFinalizor一定是在其他static类之前被初始化,在所有static类析构后再析构,这样我们在gcFinalizor类的析构函数内释放掉所有收集到的其他new的singleton,为此只需要在其他new的singleton除插入一行收集代码就可以。为了还能解决析构时控制循序,还需要在收集的时候设定一个优先级,最后根据优先级排序,这样new的singleton问题就解决了。
接下来需要使用CRT函数来分析内存泄漏,简单的方法就是在debug模式,程序结束的地方(gcFinalizor的析构函数内)调用_CRTDumpMemoryLeaks函数来dump所有分配了未被释放的内存块,但我发现简单使用这个函数会dump出来很多,尤其是在还没有解决new的singleton问题时。为此可以使用_CrtSetDbgFlag来标记某些你确信没有内存泄漏的代码,就像这样:
_CrtSetDbgFlag(0);
...
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF);
在_CrtSetDbgFlag之间的代码是不会被dump的,就算存在分配了没有释放,这需要是标定某些确信的singleton内存块,这样可以减少dump出来的信息,集中精力分析可疑代码。
还有可以使用_CrtMemCheckpoint函数来做内存状态快照,比如进入某个函数之前_CrtMemCheckpoint一次,退出后_CrtMemCheckpoint一次,然后通过_CrtMemDifference来比较前后两次内存状态的差别,最后使用_CrtMemDumpStatistics输出差别信息,在实际中,我发现这个功能很好,稍微做一下封装,使用其检查内存泄漏很方便。最后还有一个实用的函数就是_CrtMemDumpAllObjectsSince,它的作用就是dump出自某次内存快照之后的未释放内存块,如果直接传入参数NULL,其功能等同与_CRTDumpMemoryLeaks。
最后说说发现了内存泄漏信息,如何定位代码吧,通过_CRTDumpMemoryLeaks dump的可以内存泄漏信息一般是:
{xxx} NORMAL .............. zzz bytes ...........
我们只需要关心xxx和zzz就可以了,xxx表示了内存分配id,zzz表示了分配大小,通过xxx我们确定是第几次分配内存时产生未释放的问题(这依赖与你的操作,这里我们假定操作完全相同,那么xxx在多次程序运行过程是一定的,如果操作不同xxx则不完全相同,此时这个xxx的意义不大,但我们通过录制操作过程来回放保证操作相同;如果是多线程程序,而这次内存分配又是在某个线程内完成的,那么这个xxx基本没有意义),我们可以通过调用函数_CrtSetBreakAlloc(xxx)来断定定位改次内存,然后察看call stack判定代码位置,为了能不每次检查不同的xxx而重新编译代码,把xxx放在一个文件中,程序启动的时候读取这个xxx,然后调用_CrtSetBreakAlloc函数来下断点是非常重要的,要不link一个程序的时间可能等很久。
最后最为重要的是,记得要在你所有的cpp文件中加入
#include
#include
保证的内存分配是使用了可debug的_malloc_dbg函数,这样上面说那些函数才能正常工作,为了简化问题,你可以把它放在pch.hpp这类precompiler header文件文件中,一般我们工程第一个include的文件都应该是pch.hpp文件。
发布:siney | 分类:编程心得 | 评论:0 | 引用:0 | 浏览:
| TrackBack引用地址
- 相关文章:
发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。






