跟我学C++中级篇——常见的内存泄露

一、内存泄露

开头先说明重点“内存泄露不可怕,可怕是内存泄露的累积!”。这句话可是说得多了。大家仔细一琢磨就明白了,其实内存泄露一般来说象那种一次性泄露的,只要不是过于大,基本都没有什么问题。
那么什么是内存泄露?它有什么危害?为什么程序员一谈到OOM就为之色变?内存泄露其实非常容易理解,就是动态的内存没有释放导致无法回收已经不再使用的内存。它的危害非常明显,就是引起程序的崩溃,毕竟内存做为一种资源是有限。
不过OOM,Out Of Mmemory,内存不足。它和内存泄露不是必然的联系。OOM一般指内存不足,导致程序无法运行,但内存不足不一定是内存泄露导致,也有可能是分配的内存太大或者被别的程序把内存占用的太多等等。但大多数的OOM都和内存泄露有或多或少的联系。
这是需要提醒的是,一定要区分内存不足和内存泄露。两者既有区别又有联系,要掌握它们的各自的本质和相关影响的因素。内存不足是一种状态而内存泄露是一种形式或者说行为。

二、常见的形式

初步尝试着把常见的内存泄露的方法总结一下,可能前面或多或少的也有过类似的总结:
1、分配但忘记释放内存
如new或malloc等后,忘记却delete或free了。当然,也有一些新手犯一些低级错误,交叉使用new,free或malloc,delete。这样做极有可能埋下内存泄露的伏笔。
2、智能指针的循环引用
这种情况比较少见,一般遇到这种情况的大多也是不太熟练的新手。
3、因为异常或代码其它逻辑导致无法进入回收内存的代码
这种情况非常常见,甚至多年的老鸟都会犯这种错误。比如在分配内存后,调用一个函数,这个函数处理产生了异常,导致程序回退到异常处理,但异常处理又不在这个逻辑内。或者说某个Case跳转了到一个不相干的模块等等。
4、类中控制着多层的嵌套动态内存分配,析构函数未正确处理
这种更常见,这个有点类似于深拷贝和浅拷贝。对象中嵌套对象,对象中管理着不同的动态内存。一个不小心就有可能造成野指针。
5、内存的重用
也就是前面提到的在指定的内存上创建对象,如果只是回收了指定的内存,则一不小心不有可能造成内存的泄露。
6、分配的数组删除使用的普通删除
这种情况一般也多见于新手,应该使用delete[]结果使用了delete
7、分配的数据类型和删除时的数据类型不一致
这个比较少见,就是比如用int类型分配的空间,但在应用中需要转成char,结果在释放时没有回归到原来的地方直接就释放了(只截取了部分)。
8、继承中有内存需要析构但未使用虚析构函数
这种情况比较多见,但在老鸟中就罕见了。一般都是建议管它有没有内存需要都把继承中的析构函数虚拟化。
9、使用标准库或其它库中的不当行为
比如标准库的容器中存储的普通指针,一些库的自己的内存管理应用没弄明白等等,都可能引起内存的泄露。这就需要不断的去看相关的文档资料等,不要大意。
10、其它
其它就是个大锅,啥都可以装,但基本上装到这个锅里的,都是很少见甚至罕见的内存问题了。有可能很多问题都是场景性非常强的,大家自己明白就好。

三、例程

下面对上面的内存泄露情况写代码示范一下:

#include <iostream>
class A {
public:
  A() {
    if (nullptr == pa) {
      pa = new char[10];
    }
  }
  /*virtual*/ ~A() {
    if (nullptr != pa) {
      delete[] pa;
    }
  }

private:
  char *pa = nullptr;
};
class B : public A {
public:
  B() {
    if (nullptr == pb) {
      pb = new char[10];
    }
  }
  ~B() {
    if (nullptr != pb) {
      delete[] pb;
    }
  }

private:
  char *pb = nullptr;
};
void MemLeak() {}
void ThrowRet() { throw 1; }
int main() {
  //---------------------------------
  A *a = new B();
  delete a;

  //---------------------------------
  char *buf = new char[10];

  //---------------------------------
  char *d = new char[10];
  delete d;

  //---------------------------------
  int *pData = new int;
  *pData = 8;
  char *pDataChar = (char *)pData;
  delete pDataChar;

  //---------------------------------
  char *p = new char[10];

  try {
    ThrowRet();
  } catch (...) {
    return 0;
  }

  delete[] p;

  return 0;
}

代码很简单,但当简单的东西应用在复杂的场景中,代码一多起来,设计一复杂,就可能出现这些问题。这就是老师经常说的,一讲就明白,一用就犯错。

四、总结

需要内存但无法分配出来(OOM)和内存泄露很容易混淆。但更容易让C/C++开发人员难受的是复杂的内存管理,一个不小心就可能出现泄露。很多的底层库其实也偶尔会被发现有内存泄露的现象,这说明内存泄露看上去容易控制其实在实际应用中非常复杂。
不过也不用担心,内存泄露是非常容易复现的。那么解决起来,至少有个目标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值