前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >工作中常见的几种内存泄漏场景汇总

工作中常见的几种内存泄漏场景汇总

作者头像
CPP开发前沿
发布2022-06-04 17:36:22
9020
发布2022-06-04 17:36:22
举报
文章被收录于专栏:CPP开发前沿CPP开发前沿

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

作为C/C++程序员,谁还不写Bug,Bug里面的王者要数内存泄漏,内存泄漏具有其独有的属性,比如说:隐蔽性强、难以排查、占用资源不断累积等特点,更甚者是会让人想要摔键盘……

本文主要是对工作中经常遇到的内存泄漏场景进行总结,与君共勉。

1、在构造函数中抛出异常

代码语言:javascript
复制
class Test
{
public:
    Test(int iFlag)
    {
        m_pBuf = new char[4*1024*1024];
        if(!iFlag)
        {
            throw("Exception:验证构造函数抛出异常");
        }
    }
    ~Test()
    { 
        std::cout<<"释放类中申请的资源"<<std::endl;
        delete [] m_pBuf;
    }
private:
    char *m_pBuf;
};

int main()
{
    Test cTest(0);
    return 0;
}

如上面的代码所示,代码在编译器中编译正常,声明cTest类实例后如果传入参数为真,则可以正常运行且类中new的资源可以正常释放。但是当传入参数为0时,运行代码后抛出异常。进程退出,异常信息如下图所示:

从结果可以看出,抛出异常后代码退出,但是类的析构函数没有被调用。这也说明如果在构造函数中抛出异常,类的析构函数是不会被调用的。所以如果要在构造函数中使用抛出异常,那么切记,一定要在抛出异常前对申请的资源进行正确的释放。反之,就像上面的代码一样,产生内存泄漏的风险。如果要将上面的代码修改正确,可以做如下修改:

代码语言:javascript
复制
Test(int iFlag)
    {
        m_pBuf = new char[4*1024*1024];
        if(!iFlag)
        {
            delete [] m_pBuf;
            throw("Exception:验证构造函数抛出异常");
        }
    }

2、匿名对象产生的内存泄漏

如下面的代码所示,代码功能定义一个临时的对象,定义好后没有使用指针对其进行指向,在程序退出时,临时对象申请的资源就不会进行释放,使用内存检测工具后,就会提示内存泄漏风险。

代码语言:javascript
复制
int main()
{
    std::cout<< (*new std::string("hello world"))<<std::endl;
    return 0;
}

如代码所示,上面这段代码既能通过编译,又能输出正确的结果,但是却存在内存泄漏风险。这是因为在上面的代码中使用了new在堆上申请了资源且成功分配了空间。如果在代码运行结束后没有使用delete进行释放,那么就会产生内存泄漏。如果想逃过内存泄漏工具的检测,只要将*new std::string("hello world")赋值给一个指针,然后在退出前对指针进行释放即可。

3、基类中的析构函数引发的内存泄露

在C++中,如果子类的对象是通过基类的指针进行删除,如果基类的析构函数不是虚拟的,那么子类的析构函数可能不会被调用,从而导致派生类资源没有被释放,进而产生内存泄漏。如下面的代码所示:

代码语言:javascript
复制
class TBase
{
public:
    virtual int DoSomeThing(){std::cout<<"TBase::DoSomeThing"<<std::endl;}
    virtual ~TBase(){}
};

class Test:public TBase
{
public:
    Test()
    {
        m_pBuf = new char[12];
        memset(m_pBuf,0x0,sizeof(m_pBuf));
    }
    ~Test()
    { 
        std::cout<<"Test子类释放类中申请的资源"<<std::endl;
        delete [] m_pBuf;
    }
    
    int DoSomeThing(){
        std::cout<<"Test::DoSomeThing"<<m_pBuf<<std::endl;
        return 0;
    }
private:
    char *m_pBuf;
};

int main()
{
    TBase *pBase = new Test();
    delete pBase;
    return 0;
}

如代码所示,在main函数中定义了一个基类的指针,并指向其子类对象,随后对基类指针进行释放,本意是想通过对基类指针释放同时也调用子类的析构函数释放子类资源。但是事与愿违,运行后,并没有调用子类的析构函数。这是因为,在基类中并没有定义析构函数,在这种情况下,编译器会为我们默认生成一个析构函数,但还不够智能,生成的析构函数不是虚拟的,这样在对基类指针进行析构时就不能调用子类析构函数释放资源。如果想要达到我们想要的效果,只需将基类中的析构函数定义成虚析构即可。修改后运行结果如图所示:

可见,子类中的资源得到正常释放。

4、void*指针产生的内存泄漏

如下面的代码所示,定义一个类对象后,本意想在类外统一定义一个资源释放接口对资源进行释放,但是事情却没有向我们想象的地方进行。依旧使用上面的代码,我们在这里只新增一个资源释放接口并在main函数中修改调用。

代码语言:javascript
复制
void deleteObj(void *pObj)
{
    delete pObj;
    std::cout<<"delete pObj"<<std::endl;
}
int main()
{
    Test *pBase = new Test();
    deleteObj(pBase);
    return 0;
}

上面的代码乍一看是没什么问题的,但是仔细考虑下,将pBase传入到deleteObj后,pBase被转换成void*类型,然后再释放。但是这样做就破坏了delete的工作原理,delete删除对象时,先调用对象的析构函数,再delete指针对象,上面的代码在将pBase转换成void*后,delete获取不到析构对象的类型就不能正确调用对象的析构函数,因此导致析构对象可能产生内存泄漏。

5、容器元素产生的内存泄漏

容器元素产生的内存泄漏主要是当容器中的元素为指针时,每次new一个对象都会将指针保存在容器中,清理容器时,容器中的指针对象不会同时被清理。

在编码时,如果容器中保存的是对象,那么容器会自动对对象进行清理,但如果是指针,则需要编码人员手动对容器中保存的指针进行清理,如下面的代码所示,在这里依旧复用前面章节的代码,本次只对main函数进行改造:

代码语言:javascript
复制
int main()
{
    std::vector<Test *> vTest;
    for(int i=0;i<10;i++)
    {
        vTest.push_back(new Test());
    }
    vTest.clear();
    return 0;
}

如上所示,程序结束时仅使用clear方法对vector资源进行清理,但是,保存在vector中的指针对象并没有被清理掉,需要我们手动进行处理,要想得到正确的代码,按照如下方式进行修改:

代码语言:javascript
复制
for(int i=0;i<vTest.size();i++)
{
    delete vTest[i];
}

代码运行后,从结果可知,资源被正确释放,如下图所示:

6、写在最后

开发有bug是正常的,要学会学习bug,不断的总结编程过程中遇到的问题,才能让我们更好的解决问题,最终把问题消灭在写代码之前。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CPP开发前沿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档