C++内存泄露注意点

本文参考点这里,大部分内容还是一样的,一些地方加上了自己的理解,因为文字是自己边看边写的,所以作为原创了,大家可以看看原博文对比。

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

两种情况下会出现这种内存泄露:
1.在里面创建了对象占用了内存,但是没有显示地释放对象占用的内存。(针对整个对象,显示释放指的是手动回收内存
2.在类的构造函数中动态地分配了内存,但是在析构函数中没有释放内存或没有正确地释放内存。(类中的构造和析构中必须要有配对的new和delete,否则对象销毁的时候会残留未释放的数据在栈上)

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

多个指针没有全部清空

3.在释放对象数组时delete中使用方括号的注意点

方括号是告诉编译器这个指针指向的是一个对象数组,同时也告诉编译器正确的对象地址值并调用对象的析构函数,如果没有方括号,那么这个指针就被默认认为只指向一个对象(这个在以前的博文中提到过,C++的数组会自动转换成指向第一个元素的指针),对象数组中的其他对象的析构函数就不会被调用,会引发内存泄露。
(假设这是一个对象数组,每个对象需要对应的析构函数才能清除,也不能直接清除数组,正确的做法是对数组中的元素一个个调用析构)

还需要注意,在释放二维数组时候通常这么使用delete []p[capacity]:

1.如果在第二个[]中放了一个比对象数组的capacity还大的数字,那么编译器会调用无效对象的析构函数(内存溢出),会造成堆的崩溃。
2.如果反过来比capacity小的话:编译器就不能调用足够多个析构函数,结果会造成内存泄露。
一维数组的话就没必要考虑了,直接delete []p即可

finally:释放单个对象,单个基本数据类型的变量或者是基本数据类型的数组不需要方括号[](例:char* c = new char[10],delete c即可,当然你这么写delete []c也没错 ),释放定义了析构函数的对象数组需要方括号[](最好就是直接delete []p,如下图,系统会调用数组中每个对象的析构函数销毁对象)。

#include<stdlib.h>
#include<iostream>
using namespace std;
class A{
public:
        A(){cout<<"1.constructor~~"<<endl;}
        ~A(){cout<<"2.destory~~"<<endl;}
};
int main()
{
        A* p = new A[3];
        delete []p;
        return 0;
}

输出如下图:
这里写图片描述

二维数组的话一般如下:

#include <iostream>
using namespace std;

int main(void)
{
        int **p;
        p = new int*[3];
        for (int i = 0; i < 3; i++)
               p[i] = new int[4];
        for (int i = 0; i < 3; i++) {
               for (int j = 0; j < 4; j++)
                       p[i][j] = i+j;
        }
        for (int i = 0; i < 3; i++)
               delete []p[i];
        delete []p;
        return 0;
}

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

这个说起来有点拗口,但是仔细看:前者是一堆指针,后者是一堆对象。
在释放后者的时候,直接调用delete []p,即可调用数组中每个对象的析构函数将其空间释放。
但在释放前者的时候,我们释放的只是指向对象的指针,并不是真的对象(虽然它们也占用了一点空间),这时候清除指针数组中的元素只是释放了这一堆指针,还需要释放它们指向的地址,这是正确的做法是用一个循环:首先:先将每个对象释放了,然后:再把指针释放

5.缺少拷贝构造函数

注意:两次释放相同的内存 是一种错误的做法,并且可能会造成堆的崩溃。(Linux下g++坚决不允许,只要发生,立马报错)
按值传递会调用(拷贝)构造函数,引用传递不会调用
在c++中,如果没有重写拷贝构造函数,编译器会调用默认(这都是一样的),会逐个成员拷贝的方式来复制数据成员,如果是以逐个成员拷贝的方式来复制指针,这种情况被定义为将一个变量的的地址赋给另外一个变量。

这样一来的话,会导致两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态内存的分配空间。如果释放第二个对象,它的析构函数会释放相同的内存,这样是错误的。

总结:如果一个类里面存在指针成员变量,要么重写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符。

6.缺少重载赋值运算符

当使用=号时,当类的大小是可变的,当原先的大内存被赋上较小的数据,就会引发部分空间空缺,也就是内存泄露。如下图:

这里写图片描述

7.关于nonmodifying运算符重载

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

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

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

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

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

造成这种指针的原因:
1.指针变量没有初始化(如果值不定,一般初始化为NULL或nullptr)
2.指针被free或delete后,没有设置为NULL,free和delete只是把指针所指向的内存给释放掉,并没有把指针本身给干掉(这就是我们上面提到的指向对象的指针),此时的指针是“垃圾”内存,释放后的指针应该被置为NULL。
3.指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。(不可能返回到栈上去,这个地址应该是无效的,函数调用堆栈之后结束销毁,自身在栈上面的地址也被销毁,这时候的地址是无效的(虽然存在但是那是其他的未知的程序的了),就是野指针了

你要访问栈内存也行,但前提条件是在函数运行范围内。

并且注意:栈本身主要是由编译器静态维护和管理,所以程序中一般情况下应该避免使用指向栈的指针。

有什么错误还希望指出,共同进步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值