C/C++中如何判断C内存没释放是否存在


  网络上有很多关于C++单例模式嘚帖子其中不乏精品之作。本篇文字在吸收了精华之余仅作了个人的一点点总结。
  通过new出一个对象来实现的单例不论单例是通過饿汉方式,还是懒汉方式来实现都面临一个问题,即new出来的对象由谁释放何时释放,怎么释放 简单的实现可以参考
  如果对象沒有被释放,在运行期间可能会存在C内存没释放泄露问题有人可能会说,在进程结束时操作系统会进行必要的清理工作,包括释放进程的所有堆栈等信息即使存在C内存没释放泄露,操作系统也会收回的;且对于单例来讲进程运行期间仅有一个对象实例,而且该实例囿可能根本就没有进行C内存没释放的申请操作不释放实例所占C内存没释放,对进程的运行也不会造成影响这么说好像很有道理的样子,既然操作系统会清理一切后续工作那么我们还有必要进行C内存没释放释放工作吗?

  闲话少叙言归正传。作为一个非著名的程序猿我和大多数同类一样,对代码有着不一般的情结且有强迫症。成对的使用new和delete是程序猿们最基础的素养这么看来,单例对象的释放必须要在代码中体现出来

方式一:由程序猿在程序结束之前,通过调用delete来释放

很自然的可能会有部分猿/媴们[其实就是我啦^-^]想到,把释放工作交给析构函数来处理不就行了想法是不错,代码要怎么写呐?可能如下:

可惜的是一:new出来的对象,必须用与之对应的delete显示的来释放程序并不会自动调用析构函数来析构new出来的对象;二:在delete的时候会调用析构函数,析构函数中又调用了delete然后又调用了析构函数……这样就进入了一个无限的循环之中。

通过valgrind工具我们可以看到,所有C内存没释放都被释放了这种处理完成了任务,好像无可厚非但是,大多数情况下这条语句会被遗忘,如果程序中存在多个单例也很容易将某个对象嘚释放操作遗漏。

方式二:通过C标准库的atexit()函数注册释放函数

  atexit()函数可以用来注册终止函数如果咑算在main()结束后执行某些操作,可以使用该函数来注册相关函数

  标准规定atexit()至少可以注册32个终止函数,如果系统中有多个单例我们可能要注册多个函数,或者在同一个终止函数中释放所有单例对象但是方式一中的问题依然存在。必须由程序猿/媴手工注册且有可能遗漏某个对象。

方式三:由单例类提供释放接口

  本方式由单例类提供一个释放对象的函数在该函数内部進行对象的释放操作。其本质与方式一并无太大差别同样的继承了方式一的缺点。

  虽然这是一种可行的释放对象方式但是这种方式并没有明显的优点。这不是我们想要的方案

方式四:让操作系统自动释放

  我们知道,进程结束时静態对象的生命周期随之结束,其析构函数会被调用来释放对象因此,我们可以利用这一特性在单例类中声明一个内嵌类,该类的析构函数专门用来释放new出来的单例对象并声明一个该类类型的static对象。

好了我们可以像之前一样使用单例了,不需要再关心对象的释放问题进程结束时,操作系统会帮我们去释放的

  之所以要进行C内存没释放的释放,是因为在单例的实现过程中我们使用了new来创建对象。如果在实现过程中不使用new,而是使用静态[局部]对象的方式就不存在释放对象的问题了。


}

平时我们在写代码时候有思考過要主动去释放vector的C内存没释放吗?

1、对于数据量不大的vector没有必要自己主动释放vector,一切都交给操作系统

2、但是对于大量数据的vector,在vector里面嘚数据被删除后主动去释放vector的C内存没释放就变得很有必要了!

读者可以新建一个控制台程序,把代码运行起来看输出且看代码:

}

在Windows下微软给我们提供了一个十分強大的C/C++运行时库这个运行时库中包含了很多有用的功能。而众多强大功能之一就是C内存没释放泄露的检测

C/C++提供了强大的C内存没释放管悝功能,不过随之而来的却是C内存没释放管理的复杂问题C内存没释放泄露、踩C内存没释放等问题随之大量产生。要完全杜绝这些问题是仳较困难不过一个高效有用的工具却可以将C内存没释放泄露的问题第一时间发现并处理掉。

VS的C/C++运行时库中C内存没释放管理系统的基础就昰调试堆调试堆和普通的堆不同之处就在于每一块分配的C内存没释放前后都有一些额外的信息。下面就是每块分配C内存没释放的额外信息: 

从上面的定义我们看到每一块分配的C内存没释放(假设的data变量)前后都有一个NoMansLoad的gap这个gap会填充一些数据,检测是否C内存没释放操作超絀了合法的范围szFileName和nLine则表示该C内存没释放是从什么文件的哪一行分配的。nDataSize表示该块C内存没释放的实际大小nBlockUse表示当前分配的C内存没释放的類型号是什么,类型号稍后会有详细解释lRequest表示当前分配的序号,这个序号是进程唯一的第一次分配的为1,第二次为2以此类推。

C/C++无论使用new、malloc、strdup等最后都会到一个CRT的内部函数:_heap_alloc_dbg_impl这个函数的作用就是填充上面看到的C内存没释放块的头和尾,然后做一些HOOK函数以及检查调试堆嘚工作

其中几个对C内存没释放泄露检测有用的函数是:

下面我们通过几个实例来简单介绍C内存没释放泄露的使用方法:

其中,绿色部分表示分配的C内存没释放序号也就是该C内存没释放为第几块C内存没释放。红色部分为C内存没释放块的有效长度蓝色部分为该C内存没释放嘚前16个字节。橙色部分为该C内存没释放的地址

有了这些我们已经能基本定位非常简单的C内存没释放泄露了。不过稍复杂一点的程序都有佷多出口在每个出口都放一个_CrtDumpMemoryLeaks是不合适的。同时很多全局变量也是在main之后析构的所以实际中我们更加愿意使用下面的方法。

也就是说_CrtDumpMemoryLeaks僅能保证获取当前尚未释放的C内存没释放而通过设置_CRTDBG_LEAK_CHECK_DF标志对于C内存没释放泄露检测更加有意义!

遗憾的是MFC使用的前一种,所以MFC默认检测嘚C内存没释放泄露意义不大

3. 获取C内存没释放泄露的位置

我们看到红色的文件名和绿色的行号都出现了。对于这个我们只需要非常简单地增加两行语句:

C++中的new可以支持替换这给我们提供了一个非常方便的解决方案。而CRT也提供了Debug版本的new原型如下:

所以我们的思路就是将原來的new替换为debug版本的new。

块类型是CRT的一个重要功能通过它我们可以让C内存没释放分配检查的粒度更加细化。所谓的快C内存没释放就是每次分配的时候指定的块号块类型目前总共五种:

_NORMAL_BLOCK:就是我们日常分配的默认类型;

_CLIENT_BLOCK:就是给我们自己用的客户类型,可以指定子类型号也昰我们需要重点关注的类型;

_FREE_BLOCK:已经释放的块,因为CRT可以让链表上保留已经释放的块来模拟C内存没释放不足的情况所以这类C内存没释放┅般被填充0xDD,块类型标为_FREE_BLOCK

_IGNORE_BLOCK:有可能在一段时间内关闭调试堆操作。在该时间段内C内存没释放块保留在列表上,但被标记为“忽略”块

那么_CLIENT_BLOCK是如何使用的呢?还记得调试堆中的nBlockUse吧这是一个32位的数据,其中低16位表示块的大类型而高16位则表示子类型。所以每次我们申请C內存没释放的时候给出的块类型都包含了大类型和子类型分别设置他们的高16位和低16位即可。

下面我们用代码简单说明如何使用客户端数據块

}

我要回帖

更多关于 C内存没释放 的文章

更多推荐

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

点击添加站长微信