内存泄露问题改进(转自vczh)

参考:http://www.cppblog.com/vczh/archive/2010/06/22/118493.html

参考:https://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html

C++实谓各种语言中的软肋,功能强大,陷阱更强大。当然我认为一门语言用得不好完全是程序员的责任,不过因为C++涉及到的概念实在是太多,想用好实在也不是一件容易的事情。C++开发的时候总是会遇到各种各样的问题,其中最严重的无非是内存相关的。C语言由于结构简单,内存处理起来虽然不得力,但总的来说惯用法已经深入人心,因此也不会造成什么很难发现的错误。C++就不一样了。有了虚函数、构造函数、析构函数、复制构造函数和operator=重载之后,还是有很多人喜欢把一个类直接写进文件流,或者拿来memset,代码一团乱麻,不知悔改也。但是不能因此因噎废食,就像某人因为C++带来的心智问题太多,自己搞不定,自己团队也搞不定,就说C++不好一样。

    因此第一篇文章主要针对内存来讲。我们处理内存,第一件事就是不要有内存泄露。内存泄露不能等到测试的时候,通过长时间运行程序并观察任务管理器的方法来做,这显然已经晚了。幸好Visual C++给了我们一个十分好用的工具:_CrtDumpMemoryLeaks函数。这个函数会在Debug模式下往Visual Studio的output窗口打印出那个时候你new(malloc)了但是还没delete(free)的所有内存块的地址、长度、前N个字节的内容和其他信息。怎么做呢?其实很简单:

 #define _CRTDBG_MAP_ALLOC       // 添加该条语句可以显示内存泄露的文件
 #include <stdlib.h>
 #include <crtdbg.h>
 #include <windows.h>
 
 int wmain(vint argc , wchar_t* args[])
 {
     // 这里运行程序,并在下面的函数调用之前delete掉所有new的东西
     _CrtDumpMemoryLeaks();
     return 0;
 }


    我们只需要在注释的地方完成我们程序的功能,然后确信自己已经delete掉所有应该delete的东西,最后_CrtDumpMemoryLeaks()函数调用的时候就可以打印出没被delete的东西了。这个方法十分神奇,因为你只需要在main函数所在的cpp文件这么#include一下,所有的cpp文件里面的new都会受到监视,跟平常所用的用宏把new给换掉的这种破方法截然不同。如果你使用了全局变量的话也要小心,因为全局变量的析构函数是在main函数结束之后才执行的,因此如果在全局变量的析构函数里面delete的东西仍然会被_CrtDumpMemoryLeaks函数当成泄露掉的资源对待。当然本人认为全局变量可以用,但是全局变量的赋值必须在main里面做,释放也是,除非那个全局变量的构造函数没有申请任何内存,所以这也是一个很好的检查方法。

(添加:因为这里是针对程序运行结束后进行判断是否存在内存泄露,所以可以配合自建malloc函数添加打印获取分配的内存地址,来配合判断是否存在内存泄露以及内存泄露的地址~);


    不过上面也仅仅是一个告诉你有没有内存泄漏的方法罢了。那么如何避免内存泄露呢?当然在设计一些性能要求没有比操作系统更加严格的程序的时候,可以使用以下方法:
    1、如果构造函数new了一个对象并使用成员指针变量保存的话,那么必须在析构函数delete它,并且不能有为了某些便利而将这个对象的所有权转让出去的事情发生。
    2、在能使用shared_ptr的时候,尽量使用shared_ptr。shared_ptr只要你不发生循环引用,那么这个东西可以安全地互相传递、随便你放在什么容器里面添加删除、你想放哪里就放在哪里,再也不用考虑这个对象的生命周期问题了。
    3、不要在有构造函数和析构函数的对象上使用memset(或者memcpy)。如果一个对象需要memset,那么在该对象的构造函数里面memset自己。如果你需要memset一个对象数组,那也在该对象的构造函数里面memset自己。如果你需要memset一个没有构造函数的复杂对象,那么请为他添加一个构造函数,除非那是别人的API提供的东西
    4、如果一个对象是继承了其他东西,或者某些成员被标记了virtual的话,绝对不要memset。对象是独立的,也就是说父类内部结构的演变不需要对子类负责。哪天父类里面加了一个string成员,被子类一memset,就欲哭无泪了。
    5、如果需要为一个对象定义构造函数,那么连复制构造函数、operator=重载和析构函数都全部写全。如果不想写复制构造函数和operator=的话,那么用一个空的实现写在private里面,确保任何试图调用这些函数的代码都出现编译错误。
    6、如果你实在很喜欢C语言的话,那麻烦换一个只支持C不支持C++的编译器,全面杜绝因为误用了C++而导致你的C坏掉的情况出现。

    什么是循环引用呢?如果两个对象互相使用一个shared_ptr成员变量直接或者间接指向对方的话,就是循环引用了。在这种情况下引用计数会失效,因为就算外边的shared_ptr全释放光了,引用计数也不会是0的。

原文地址:https://www.cnblogs.com/xiaochouk/p/9863621.html