C++ 内存管理 —— 如何避免内存泄漏

内存泄漏

  • 1、什么是内存泄漏以及发生情况
  • 2、如何避免内存泄漏
  • 参考

1、什么是内存泄漏以及发生情况

1、定义

wikipedia中这样定义内存泄漏:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

简单来说:内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

2、危害

只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种征兆:从性能不良(并且逐渐降低)到内存完全用尽。 更糟的是,泄漏的程序可能会用掉太多内存,以致另一个程序失败,而使用户无从查找问题的真正根源。 此外,即使无害的内存泄漏也可能是其他问题的征兆。
在以下情況,内存泄漏导致较严重的后果:

  • ·程序运行后置之不理,并且随着时间的流失消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被运行后很多年内都置之不理);
  • ·新的内存被频繁地分配,比如当显示电脑游戏或动画视频画面时;
  • ·程序能够请求未被释放的内存(比如共享内存),甚至是在程序终止的时候;
  • ·泄漏在操作系统内部发生;
  • ·泄漏在系统关键驱动中发生;

3、原因和场景

对于C++的内存泄漏,总结一句话:就是new出来的内存没有通过delete合理的释放掉!

  • 1、在类的构造函数和析构函数中没有匹配的调用new和delete函数
    两种情况下会出现这种内存泄露:一是在堆里创建了对象占用了内存,但是没有显示地释放对象占用的内存;二是在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存
char * FunA()
{
	char *p = new char;
	return p;
}
void FunErrorB()
{
	char *b = FunA()//忘记delete p	
}
  • 2、在释放对象数组时在delete中没有使用方括号
    • 方括号是告诉编译器这个指针指向的是一个对象数组,同时也告诉编译器正确的对象地址值并调用对象的析构函数,如果没有方括号,那么这个指针就被默认为只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果造成了内存泄露。如果在方括号中间放了一个比对象数组大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的奔溃。如果方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个析构函数,结果会造成内存泄露。
    • 释放单个对象、单个基本数据类型的变量或者是基本数据类型的数组不需要大小参数,释放定义了析构函数的对象数组才需要大小参数。
Void FunErrorA()
{
	Char *p = new char[10];
	Delete p;//错误
}
  • 3、指向对象的指针数组不等同于对象数组
    • 对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间

#include ;

using namespace std;
class T 
{
public:
    T() 
    {
        cout <<"构造T"<<endl;
    }
    ~T() 
    {
        cout <<"析构T"<<endl;
    }
};
int main()
{
    int NUM = 3;

    T *p1 = new T[NUM];
    cout<<p1<<endl;
    delete[] p1;//正确释放语句
    cout<<"释放了p1空间"<<endl;

    T *p2 = new T[NUM];
    cout<<p2<<endl;
    delete[] p2;//正确释放语句
    cout<<"释放了p2空间"<<endl;

    system("pause");
    return 0;
}

  • 指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了。

  • 4、delete掉一个void*类型的指针,导致没有调用到对象的析构函数,析构的所有清理工作都没有去执行从而导致内存的泄露;

class Object {
private:
    void* data;
    const int size;
    const char id;
public:
    Object(int sz, char c):size(sz), id(c){
    data = new char[size];
    cout << "Object() " << id << " size = " << size << endl;
    }
    ~Object(){
    cout << "~Object() " << id << endl;
    delete []data;
    }
};

int main() {
Object* a = new Object(10, 'A');//Object*指针指向一个Object对象;
void* b = new Object(20, 'B');//void*指针指向一个Object对象;
delete a;//执行delete,编译器自动调用析构函数;
delete b;//执行delete,编译器不会调用析构函数,导致data占用内存没有得到回收;

cout << "Press any key to continue... ..." << endl;
getchar();
return 0;
}
  • 5、没有将基类的析构函数定义为虚函数
    • 当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露

2、如何避免内存泄漏

通常来说,一个线程的栈内存是有限的,通常来说是 8M 左右(取决于运行的环境)。栈上的内存通常是由编译器来自动管理的。当在栈上分配一个新的变量时,或进入一个函数时,栈的指针会下移,相当于在栈上分配了一块内存。我们把一个变量分配在栈上,也就是利用了栈上的内存空间。当这个变量的生命周期结束时,栈的指针会上移,相同于回收了内存。

由于栈上的内存的分配和回收都是由编译器控制的,所以在栈上是不会发生内存泄露的,只会发生栈溢出(Stack Overflow),也就是分配的空间超过了规定的栈大小。

而堆上的内存是由程序直接控制的,程序可以通过 malloc/free 或 new/delete 来分配和回收内存,如果程序中通过 malloc/new 分配了一块内存,但忘记使用 free/delete 来回收内存,就发生了内存泄露。

具体相关方法:

  • 1、不要手动管理内存,可以尝试在适用的情况下使用智能指针。
  • 2、使用string而不是char*。string类在内部处理所有内存管理,而且它速度快且优化得很好。
  • 3、除非要用旧的lib接口,否则不要使用原始指针。
  • 4、在C++中避免内存泄漏的最好方法是尽可能少地在程序级别上进行new和delete调用–最好是没有。任何需要动态内存的东西都应该隐藏在一个RAII对象中,当它超出范围时释放内存。RAII在构造函数中分配内存并在析构函数中释放内存,这样当变量离开当前范围时,内存就可以被释放。

(注:RAII资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源)

  • 5、使用了内存分配的函数,要记得使用其想用的函数释放掉内存。可以始终在new和delete之间编写代码,通过new关键字分配内存,通过delete关键字取消分配内存。

C++ 如何避免内存泄漏

参考

1、https://zhuanlan.zhihu.com/p/51898119
2、https://blog.csdn.net/invisible_sky/article/details/78205461

你可能感兴趣的:(C++,如何避免内存泄漏)