C++中的内存泄漏

C++中的内存泄漏

参考、转载自大神博客

1、new(malloc)和delete(free)没有配套使用

在堆里创建了对象占用了内存,但是没有显示地释放对象占用的内存
这种是最简单,最直接的内存泄露,很容易发现

2、类的构造函数和析构函数中new等没有配套使用

在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存

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

如下:

  int* a = new int;
  int** b = new int*;
  *b = a;

这样的情况下,如果你先把b释放了,a自身的值就没了,导致a原来申请的空间现在没人管了,造成了内存泄漏。

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

这个其实有个经典的例子,再说例子之前先说一下基本知识
在使用new,来创建一个指向class的指针的时候,new做的事情有三个:

  1. 分配内存
  2. 指针转型
  3. 调用构造函数

其中第二个这里不讨论,然后再看delete做的事情:

  1. 调用析构函数
  2. 释放内存

接下来看例子:
我们定义拥有三个指针的指针数组,每个指针指向一个class(下图左半部分)
每个class里面包含指针,指向了其他地方(下图右半部分)
在这里插入图片描述
我们使用 delete [] 的时候,编译器知道,我要释放三个class,
然后依次调用三个析构函数,将右半部分的空间释放了
然后就是释放左半部分的空间。
这是正确的做法。

在这里插入图片描述
还是刚刚一样结构的指针数组,
此时我们使用 delete ,编译器以为,自己要释放一个class
然后就只调用了第一个class的析构函数(右半部分空框框的空间被释放了)
但是其余两个class并没有调用析构函数(右半部分问号框框的空间没被释放)
然后编译器就把左半部分的空间给释放了
导致那两个class的空间无法控制了,造成了内存泄露
这是错误的做法

理解之后,我感觉这种内存泄漏有种 2 3 结合的意思:
因为嵌套导致了class的new和delete不配套

说这么多的原因是提醒大家不要对 delete[] 造成误解,
用不用delete的方括号,影响的并不是数组本身的空间(左半部分),数组只要调用了delete就会被释放
他影响的主要是数组内容指向的空间(右半部分),可能导致因为没有调用析构函数而造成内存泄露
(这一部分主要参考侯捷的视频讲解,视频讲解地址,这个视频并不只是讲了这些东西,这部分是在视频的42分钟开始讲的,上面两个图也是视频中的截图)

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

这种泄露有点像第四种,

  1. 对象数组:数组的每个元素是对象本身
  2. 指向对象的指针数组:数组的每个元素是指针,每个指针指向的是对象

小窍门: 理解各种相似名字的时候,先不要看他的修饰词,就只看名词本身。
指向对象的指针数组:他就是一个数组,数组的每个元素是指针
数组指针:他就是一个指针,是指向数组的指针

针对对象数组:
释放的时候,直接 delete[] 就可以,刚刚讨论过了
针对指向对象的指针数组:
直接 delete[] 是不行的,因为他释放的是指针的空间,指针指向的空间并没有释放(析构函数没有调用)
应该先遍历数组,依次调用每个元素的析构函数
最后使用 delete[],将数组中的每个指针释放了

6、缺少拷贝构造函数

这部分主要参考侯捷视频,视频链接,是从22分钟开始将这部分的内容

缺少的意思并不是没有,系统有默认的,但是默认的并不能达到所有要求,这时候就必须自己再写一个

这种泄露是因为两次调用了delete,
两次释放相同的内存是一种错误的做法,同时可能会造成堆的奔溃。

这里,我分了三种情况(主要讨论后面两种情况)

  1. 按值传递(默认、自己写)
  2. 指针传递(默认)
  3. 指针传递(自己写)

1. 按值传递(默认、自己写)
这种情况,拷贝构造函数不管是默认的还是自己写的,都是将值复制一份,然后给class,没什么说的
2. 指针传递(默认)
这种的话,可以解决大部分情况,但是也有不能解决的情况。
假设我们有这一个class a,他内部有个指针,指向了hello,已经分配过了空间
在这里插入图片描述
这时候,我们new一个class b,让他等于class a,如果没有写新的拷贝构造,他就会调用默认的,
默认的他只一个功能,一个对一个的复制。最终会变成这样
在这里插入图片描述两个class里面的指针都指向了hello这个空间
一旦这时候,调用了class a或者class b的析构函数,hello所在的空间会被释放,
这时候,另一个class的指针虽然还是指向那个空间,但是,内容已经没有了,这种指针有一个名字 “悬空指针”
不管之后是使用这个指针,还是调用析构函数释放这个指针,都会引发不可预知的错误。
这就是内存泄露

这种形式的拷贝也有一个名字 “浅拷贝”

3. 指针传递(自己写)
发现了默认拷贝构造函数的问题,我们就需要自己写一个。
过程:

  1. 给class b的指针分配空间
  2. 使用复制函数(strcpy),将class a中指针指向的hello复制给class b一份。

执行之后是这样的
在这里插入图片描述这样的就没有内存泄露

这种方式的拷贝叫 “深拷贝”

7、缺少重载赋值运算符

这种情况与 6 相似,是因为默认的赋值运算符,并没有特殊的操作。比如下面这种

在这里插入图片描述
执行之后,P1原来指向的空间,因为没有调用delete,导致失控
而且,赋值之后,P1和P2指向了同一块空间,这种情况刚刚讨论过,也会导致内存泄漏

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

这个我不理解,照抄了

  1. 返回栈上对象的引用或者指针(也即返回局部对象的引用或者指针)。导致最后返回的是一个空引用或者空指针,因此变成野指针
  2. 返回内部静态对象的引用。
  3. 返回一个泄露内存的动态分配的对象。导致内存泄露,并且无法回收

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

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

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

10、补充

  1. 多次调用delete
    这种情况语法检查并不会提示你,但是编译运行的过程中会出错
    例子: 两个指针指向同一块东西,释放空间的时候调用两次delete,但是不清楚这种情况算不算内存泄漏,就暂时写上吧。
    其实他也算new和delete没有配套使用,因为两个指针指向同一块东西,说明只调用了一次new。
  2. 分配内存的操作太多,没有来得及释放
    这种情况严格来说并不算内存泄漏,因为从逻辑上看并没有造成内存泄漏。
    但是如果一直调用new,会导致这样的情况:内存空间被分配完了,程序还没有来得及调用delete,会导致一些错误。

最后不专业的总结一下:

  1. new和delete没有配套使用,及衍生的各种情况
  2. 嵌套的情况,先释放了外层,内层的空间失联,及衍生的各种情况
  3. 两个指针指向同一个空间,导致的各种情况
  4. 一个指针指向了新的空间之前,没有释放之前的空间,及衍生的各种情况
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值