内存泄漏的直观理解
一个程序跑在计算机上,程序已经跑起来并运行稳定了,照理说内存占用应该不会再大幅增加了,然而查看计算机的空闲内存却发现,随着时间的进行,空闲内存一直在不断的减少,就好像在哪个地方泄漏掉了一样。
造成内存泄漏的原因
造成空闲内存不断减少的原因是因为被占用内存不断增加,被占用内存不断增加的原因是有些该释放的内存没有及时被释放。所以说应该释放的内存而没有释放就是内存泄漏的原因。所以如果要找内存泄漏的地方就要去找那些容易忘记释放内存的地方。
一般不会忘记释放内存的场景
构造函数中new出来的动态内存,一般都会记得在析构函数中用delete释放掉。
局部作用域new出来的动态内存,在退出作用域之前需要delete掉的场景(不需要delete掉的场景容易忘记释放,最终导致内存泄漏,放在后面介绍)。
容易忘记释放内存的场景
主要可以分为三类,分别是无法释放内存,可以释放内存但忘记了释放,只释放了部分内存(也就是说还有一部分忘记了释放)。
场景类型1:无法释放动态内存的场景
以引用方式返回局部定义的动态内存对象,当离开局部作用域后,唯一与动态内存对象有关联的就只有那个引用了,引用不同于指针,不能够通过delete来释放。从而该动态对象无法释放(当然也可以将引用的地址强行赋值给一个指针,然后用delete加该指针来释放,但这样显得太过偏门,而且一般情况下,对于一个引用,也不会想到要对它的内存进行手动释放)。需要杜绝这种返回局部动态对象引用的方式,可以用返回指针代替。
场景类型2:容易忘记释放动态内存的场景
2.1 局部动态内存对象指针:返回的局部动态内存对象的指针,使用完后,往往会忘了得到的这个指针是一个指向动态内存对象的指针,会忘了需要手动来释放该对象。解决办法是使用智能指针,不需要手动进行释放。
2.2: 动态内存变量的指针间的相互赋值:指向动态内存的指针,这些指针间相互赋值时,有时候会忘了先释放赋值符左边指针指向的动态内存。对于对象的赋值,如果对象内有指向动态内存的指针型成员,则可以将 "先释放操作” 实现在赋值运算符重载操作中。
场景类型3:只释放了部分动态内存的场景
3.1:析构函数没有定义成虚函数: 析构函数需要定义成虚函数,否则用delete加基类指针方式释放子类对象时,调用的是基类的析构函数,导致子类对象的部分成员没有被释放,从而导致内存泄漏。
3.2:释放动态内存对象数组时,只释放了第一个对象:用delete加中括号的方式通过指针释放在堆区申请的对象数组。有时会忘记指针指向的是对象数组而不是单个对象,也就是说在使用delete进行释放时会忘了加中括号,从而导致只释放了第一个动态内存对象,而后续数组元素没有被释放,从而导致内存泄漏。可以将指向对象数组的指针名取得更直观些来提醒自己delete需要释放的是一个数组。如将pObject更名为paObject (a代表Array)。
3.3:多层动态内存嵌套,只释放了最上面一层: 在堆区创建的用来存放“动态内存的指针”的数组(或容器)就是一个二层动态内存嵌套,在用delete释放时,要先释放数组(或容器)中指针指向的动态内存(也就是堆区创建的对象),然后再释放这个数组(或容器)。很容易只释放了数组或容器所占动态内存空间(也就是第一层),而忘记释放数组或容器中指针指向的内存(也就是第二层),从而导致内存泄漏。