当前位置:首页 > 技术文章 > 正文内容

c/c++开发工作中常见的几种内存泄漏场景汇总

arlanguage3个月前 (01-31)技术文章30

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

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

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

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

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时,运行代码后抛出异常。进程退出,异常信息如下图所示:

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

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

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

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

int main()
{
    std::cout<< (*new std::string("hello world"))<<std::endl;
    return 0;
}

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

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

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

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种实时线上内存泄漏检测的实现方式【linux后台开发】

内存泄漏的3个解决方案与原理实现,知道一个可以轻松应对开发

学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

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

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

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函数进行改造:

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中的指针对象并没有被清理掉,需要我们手动进行处理,要想得到正确的代码,按照如下方式进行修改:

for(int i=0;i<vTest.size();i++)
{
    delete vTest[i];
}

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

6、写在最后

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

扫描二维码推送至手机访问。

版权声明:本文由AR编程网发布,如需转载请注明出处。

本文链接:http://www.arlanguage.com/post/1878.html

标签: nginx ffmpeg
分享给朋友:

“c/c++开发工作中常见的几种内存泄漏场景汇总” 的相关文章

Nginx常用技巧

Nginx常用技巧:提高性能、安全性与稳定性Nginx作为高性能的Web服务器和反向代理服务器,广泛应用于各类生产环境中。通过一些常用的配置技巧,可以有效提高Nginx服务器的性能、安全性和可靠性。以下将介绍几项关键的配置技巧,帮助您最大化利用Nginx的优势。直达服务器选购网址:www.tsyvp...

Nginx 问题以及对应的解决方案

1. Nginx 无法启动当 Nginx 无法启动时,通常是由于以下几个原因导致的:配置文件有误。可以通过运行 nginx -t 命令检查配置文件是否存在语法错误。端口被占用。可以通过运行 netstat -tlnp 命令查看当前系统中占用该端口的进程,并将其停止或更改端口。缺少权限。Nginx 可...

给你的Nginx加个防火墙

引言朋友的一个 WordPress 站经常访问慢。看了一下日志,发现整天被扫描网站目录,如phpmyadmin 或者 SQL 文件,和被 CC攻击。找了一下,发现 ngx_lua_waf 是个不错的方案,但是太久不更新了,而且代码我看不懂,猝最后找到 oneinstack 一键包内置的 ngx_lu...

网站加载慢?让你的网站腾飞起来,LiteSpeed部署

说起LiteSpeed 还得说起我的小站,<开心洋葱网>,虽然流量不大,但是访问是真的慢,无奈经费有限,那就只能在服务器加速上动起心思来。之前一直听说 QUIC 访问网站的速度会让你感觉飞起来,那就搞下吧。我们先来看下 QUIC 、LiteSpeed都是些做什么的?QUIC(Quick...

性能优化大揭秘:从代码到架构,全方位提升系统性能的实战技巧

在现代软件开发中,系统性能优化是一个永恒的话题。无论是移动应用、Web应用还是分布式系统,性能始终是用户体验、系统稳定性以及业务可扩展性的关键因素。性能瓶颈的存在不仅可能导致用户流失,还可能增加系统维护成本,甚至影响到业务的正常运行。因此,从代码层面到架构层面,全面的性能优化是每个技术人员必须掌握的...

Nginx 重定向 HTTP 到 HTTPS 的便捷方法

问题描述 自从大规模使用 HTTPS 之后,所有的 HTTP 访问都要重定向到 HTTPS 站点。不然,客户只会输入域名,而很多浏览器又默认使用 HTTP 协议,如果我们没有提供 HTTP 访问,又不重定向,那客户将看到一个空白页(无法访问),客户会以为我们的站点有问题,毕竟客户哪里知道什么是 HT...