> 文章列表 > C/C++内存泄漏概述、分析、防范和排查

C/C++内存泄漏概述、分析、防范和排查

C/C++内存泄漏概述、分析、防范和排查

C/C++内存泄漏概述、分析、防范和排查

如需转载请标明出处:http://blog.csdn.net/itas109
技术交流Q:129518033

1. 概念

狭义上,内存泄漏是指动态分配的内存未正确的释放导致的,如new之后未delete。

广义上,不再使用的内存未能回收都属于内存泄漏,如已失效的全局map缓存、socket句柄、文件句柄等。

对于长时间运行的服务器后台程序,内存泄漏可能造成十分严重的后果,如性能下降、程序崩溃、系统崩溃等问题。

2. 内存泄漏的产生方式

2.1 常发性内存泄漏

产生泄漏的代码被多次执行,每次都会产生内存泄漏。

2.2 偶发性内存泄漏

偶发性内存泄漏只在特定场景下会触发,并产生内存泄漏。

当然,偶发性内存泄漏也是相对的,可能原来不常用的业务变为常用的业务,假设不常用业务存在内存泄漏,那此时的内存泄漏就是常发性内存泄漏。

2.3 一次性内存泄漏

产生泄漏的代码只会执行一次。

2.4 隐式内存泄漏

隐式内存泄漏是指由于释放内存时效引起的内存泄漏。

这里主要指的是不及时释放内存会引发的其他问题,如内存碎片导致无内存可分配引起的程序或系统崩溃等问题。

如:

  • 频繁的new/delete
  • free/delete执行后不立即回收内存
  • STL中 vector.clear() 不会释放空间
  • 全局缓存未设置失效机制导致缓存越来越大

3. 内存泄漏的分类

3.1 未释放

使用裸指针new之后未delete

未释放的代码示例(应该调用delete):

int main()
{char *str = new char[256];return 0;
}

3.2 未匹配

申请与释放正确的匹配:

  • malloc/free : 只申请/释放空间
  • new/delete : 申请空间,调用构造函数/调用析构函数,释放空间
  • new[]/delete[] : 申请空间,调用多次构造函数/调用多次析构,释放空间

new/delete与malloc/free的关系:

// new
void *ptr = malloc(sizeof(T)*1); // malloc分配空间
T* t = new(ptr)T; // 已分配存储中构造(placement new)// delete
t->~T(); // 析构
free(ptr); // free释放空间

未匹配的代码示例(应该调用delete[]):

#include <stdio.h>class Base
{public:Base() {printf("Base()\\n");}~Base() {printf("~Base()\\n");}
};int main()
{  Base *b = new Base[2];delete b;return 0;
}

运行结果

Base()
Base()
~Base()

3.3 虚析构

父类析构函数不为虚函数时, 当父类指针释放子类对象不会调用子类的析构函数,而产生内存泄漏。

如下示例会产生内存泄漏,把~Base()修改为virtual ~Base()则正常释放。

#include <stdio.h>class Base
{
public:Base(){str = new char[256];printf("Base()\\n");}~Base(){delete[] str;printf("~Base()\\n");}private:char *str;
};class Derived : public Base
{
public:Derived() { printf("Derived()\\n"); }~Derived() { printf("~Derived()\\n"); }
};int main()
{Base *base = new Derived;delete base;return 0;
}

运行结果

Base()
Derived()
~Base()

3.4 循环引用

为了避免内存泄漏, C++11起引入了智能指针,常见的有shared_ptr、weak_ptr以及unique_ptr等,其中weak_ptr是为了解决循环引用问题的。

循环引用的代码示例:

#include <stdio.h>
#include <memory>class B;class A
{
public:A() {}~A() { printf("~A()\\n"); }std::shared_ptr<B> m_B;
};class B
{
public:B() {}~B() { printf("~B()\\n"); }std::shared_ptr<A> m_A;
};int main()
{auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->m_B = b;b->m_A = a;printf("A.use_count: %ld\\n", a.use_count());printf("B.use_count: %ld\\n", b.use_count());return 0;
}

运行结果(注意,引用计数不为0,未调用析构函数)

A.use_count: 2
B.use_count: 2

将A中的std::shared_ptr m_A修改为std::weak_ptr m_A,即可解决循环引用问题。

A.use_count: 1
B.use_count: 2
~A()
~B()

4. 内存泄漏的防范

  • 不使用堆内存,使用栈内存
  • 不使用裸指针,使用智能指针
  • 使用RAII机制

5. 内存泄漏的排查思路

  • 代码检测(静态代码检测工具、动态内存检测工具)
  • 代码Review(未释放、未匹配、虚析构、循环引用)
  • 打印日志回溯业务,并输出内存信息
  • 最小化场景复现

License

License under CC BY-NC-ND 4.0: 署名-非商业使用-禁止演绎

如需转载请标明出处:http://blog.csdn.net/itas109
技术交流:129518033


Reference:

  1. https://baike.baidu.com/