C++中内存泄漏,内存溢出区别

C/C++中内存泄露和内存溢出的区别

注:泄露为没有释放内存,溢出为分配空间不够,数据溢出了

内存溢出(out of memory)
是指程序在申请内存时,没有足够的内存空间供其使用

  内存泄漏(memory leak)
是指程序在申请内存后,无法释放已申请的内存空间,占用有用内存

 注:内存泄漏最终会导致内存溢出,简单理解,内存溢出就是要求分配的内存超出了系统所给的。内存泄漏是指向系统申请分配内存进行使用(new),但是用完后不归还(delete),导致占用有效内存。

内存泄漏可分为4类:

1.常发性内存泄漏 

 引起内存泄漏的代码会被很多次执行,每次执行的时候都会导致内存泄漏

2.偶发性内存泄漏

        在某些特定的环境下执行引起内存泄漏的代码,才会引起内存泄漏。从以上两种内存泄漏的方式来看,测试环境和测试方法在程序生命周期的重要性是不可或缺的。

3.一次性内存泄漏

        代码只会执行一次,但总有一块内存发生泄漏,多见于构造类的时候,析构函数没有释放内存。

4.隐式泄漏

        程序运行过程中不断的分配内存,直到结束时才释放内存,但一般服务器程序会运行较长的时间,不及时释放也会导致内存耗尽以至于内存泄漏。

        综上所述,一次性内存泄漏对用户的程序维护是没有什么实质性的伤害,但在实际生活中,我们还是尽可能要避免此类的事件发生。

内存越界
        是指向系统申请一块内存后,使用时却超出申请范围。比如一些操作内存的函数:sprintf、strcpy、strcat、vsprintf、memcpy、memset、memmove。当造成内存泄漏的代码运行时,所带来的错误是无法避免的,通常会造成
        1.破坏了堆中内存内存分配信息数据
        2.破坏了程序其他对象的内存空间
        3.破坏了空闲内存块
        附:如果在之前你的程序运行一切正常,但因为你新增了几个类的成员变量或者修改了一部分代码(前提是保证你的这些修改是完全正确的)而导致程序发生错误,则因考虑是否是内存被破坏的原因了,重点排查内存是否越界。
缓冲区溢出(栈溢出)
        程序为了临时存取数据的需要,一般会分配一些内存空间称为缓冲区。如果向缓冲区中写入缓冲区无法容纳的数据,机会造成缓冲区以外的存储单元被改写,称为缓冲区溢出。而栈溢出是缓冲区溢出的一种,原理也是相同的。分为上溢出和下溢出。其中,上溢出是指栈满而又向其增加新的数据,导致数据溢出;下溢出是指空栈而又进行删除操作等,导致空间溢出。


内存泄漏的几种情况:

1、在类的构造函数和析构函数中没有匹配的调用new和delete函数

两种情况下会出现这种内存泄露:一是在堆里创建了对象占用了内存,但是没有显示地释放对象占用的内存;二是在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存。

2、没有正确地清除嵌套的对象指针

3、在释放对象数组时在delete中没有使用方括号

方括号是告诉编译器这个指针指向的是一个对象数组,同时也告诉编译器正确的对象地址值并调用对象的析构函数,如果没有方括号,那么这个指针就被默认为只指向一个对象,对象数组中的其他对象的析构函数就不会被调用,结果造成了内存泄露。如果在方括号中间放了一个比对象数组大小还大的数字,那么编译器就会调用无效对象(内存溢出)的析构函数,会造成堆的奔溃。如果方括号中间的数字值比对象数组的大小小的话,编译器就不能调用足够多个析构函数,结果会造成内存泄露。

释放单个对象、单个基本数据类型的变量或者是基本数据类型的数组不需要大小参数,释放定义了析构函数的对象数组才需要大小参数。

4、指向对象的指针数组不等同于对象数组

对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间

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

5、缺少拷贝构造函数

两次释放相同的内存是一种错误的做法,同时可能会造成堆的奔溃。(第一次释放完成之后,可能被别的程序有占用了)

按值传递会调用(拷贝)构造函数,引用传递不会调用。

在C++中,如果没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数,会逐个成员拷贝的方式来复制数据成员,如果是以逐个成员拷贝的方式来复制指针被定义为将一个变量的地址赋给另一个变量。这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的。

所以,如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符

6、缺少重载赋值运算符

这种问题跟上述问题类似,也是逐个成员拷贝的方式复制对象,如果这个类的大小是可变的,那么结果就是造成内存泄露,如下图:

C++中内存泄漏,内存溢出区别_第1张图片

7、关于nonmodifying运算符重载的常见迷思

a、返回栈上对象的引用或者指针(也即返回局部对象的引用或者指针)。导致最后返回的是一个空引用或者空指针,因此变成野指针

b、返回内部静态对象的引用。

c、返回一个泄露内存的动态分配的对象。导致内存泄露,并且无法回收

解决这一类问题的办法是重载运算符函数的返回值不是类型的引用,二应该是类型的返回值,即不是 int&而是int

8、没有将基类的析构函数定义为虚函数

当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露

野指针:指向被释放的或者访问受限内存的指针。

造成野指针的原因:

  1. 指针变量没有被初始化(如果值不定,可以初始化为NULL)
  2. 指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL.
  3. 指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。

 

你可能感兴趣的:(C语言,c++)