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:
- https://baike.baidu.com/