C++:内存泄露 内存溢出 野指针

1. 什么是内存泄露

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

内存泄漏分为以下两类

堆内存泄漏:我们经常说的内存泄漏就是堆内存泄漏,在堆上申请了资源,在结束使用的时候,没有释放归还给OS,从而导致该块内存永远不会被再次使用

系统资源泄漏:通常指的是系统资源,比如socket,文件描述符等,因为这些在系统中都是有限制的,如果创建了而不归还,久而久之,就会耗尽资源,导致其他程序不可用
 

内存溢出:没有足够内存可分配,申请内存失败

野指针:声明未初始化置空;释放未置空;越界访问;超作用域访问(栈)

2. 影响

内存泄露,内存减少浪费,程序崩溃,大量内存泄露,影响系统稳定性

频繁地分配和释放不同大小的堆空间将会产生堆内碎块

性能下降、程序终止、系统崩溃、无法提供服务

2. 原因

1. new/delete,malloc/free没有匹配

2. delete数组没有[],delete []p;

3. 多态中基类的析构函数没有定义为虚函数

3. 解决方法

(1)设计:智能指针

(2)开发:检测工具cppCheck、VS内存分析

(3)事后:检测工具VLC、分析工具windbg/pdb,mtrace,UMDH(User-Mode Dump Heap)

  • hook实现泄漏判断与追踪:实现本地检查或者在线的全局实时检查(线上版本,写一个conf配置文件,热更新是否使用hook)

4. VLD内存泄漏检测原理

1、Visual Leak Detector在Debug使用malloc,realloc,new等方式从堆中分配的内存,会在内存块的头中记录分配该内存的文件名及行号。当程序退出时CRT会在main()函数返回之后做一些清理工作,这个时候来检查调试堆内存,如果仍然有内存没有被释放,则一定是存在内存泄漏。从这些没有被释放的内存块的头中,就可以获得文件名及行号,并将其转换成报告输出。
2、Visual Leak Detector将其初始化设置在compiler段,从而使得它在绝大多数全局变量和几乎所有的用户定义的全局变量之前初始化。

3. Visual Leak Detector的工作分为3步,首先在初始化注册一个钩子函数;然后在内存分配时该钩子函数被调用以记录下当时的现场;最后检查堆内存分配链表以确定是否存在内存泄漏并将泄漏内存的现场转换成可读的形式输出。

5. 智能指针使用注意事项

C11内存管理之道:智能指针 - Memset - 博客园 (cnblogs.com)

5.1 shared_ptr

a.避免一个原始指针初始化多个shared_ptr。

b.不要在参数实参中创建shared_ptr。

c.避免循环使用,循环使用可能导致内存泄漏

d.通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质是一个裸指针,这样可能导致重复析构。

e.函数不要返回临时变量智能指针的引用或右值引用(移动构造)

f.C++11创建shared_ptr类型的数组,需要指定删除器,但unique_ptr不用指定;C++20则支持直接创建智能指针的数组

shared_ptr<int> ps(new int[10], [](int *p) {delete[] p;}); //lambda
ps[0] = 0;//error

shared_ptr<int []> ptr2(new int[10]); //error
unique_ptr<int []> ptr(new int[10]);  //ok
ptr[1] = 10;//ok

//智能指针创建数组模板
template<typename T>
shared_ptr<T> make_shared_array(size_t size) {
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}
// 使用make_shared_array,创建指向数组的
shared_ptr shared_ptr<int> p = make_shared_array<int>(10);//ok
p[0] = 0;//error
pia.get()[0] = 0; //ok

5.2 unique_ptr

unique_ptr<int, void(*)(int *)> ptr2(new int(1), [&](int *p){delete p;}); //error
unique_ptr<int, std::function<void(int*)>> ptr2(new int(1), [&](int *p){delete p;}); //ok

5.3 weak_ptr

弱引用智能指针weak_ptr用来监视shared_ptr,不会使引用技术加1,也不管理shared_ptr内部的指针,主要是监视shared_ptr的生命周期。weak_ptr不共享指针,不能操作资源,它的构造和析构都不会改变引用计数。

5.4 std::enable_shared_from_this类

sharerd_ptr不能直接返回this指针(double free类似5.1 a),需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回智能指针,因为std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观测this指针,调用shared_from_this方法时,调用了内部的weak_ptr的lock()方法,将所观测的sharerd_ptr返回。

需要注意的是,获取自身智能指针的函数仅在share_ptr<T>的构造函数调用之后才能使用,因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。

5.5 循环引用

shared_ptr的循环引用可能导致内存泄漏,通过weak_ptr可以解决这个问题,将A或者B任意一个成员变量改为weak_ptr即可。

shared_ptr<B> bptr;
weak_ptr<A> aptr;

这样在对B成员赋值时,即bp->aptr = ap,由于aptr是weak_ptr,并不会增加引用计数,所以ap的计数仍然是1,在离开作用域之后,ap的引用计数会减为0,A指针会被析构,析构之后,其内部的bptr引用计数会减1,然后离开作用域之后,bp引用计数从1减为0,B对象也被析构,所以不会发生内存泄漏。

5.6 通过智能指针管理第三方库分配的内存
C++11 智能指针 - 明明1109 - 博客园 (cnblogs.com)

如何解决由于忘记赋值导致指针提前释放的问题?
答:可以用一个宏来解决这个问题,通过宏来强制创建一个临时智能指针。代码如:

// OK
#define GUARD(p) std::shared_ptr<void> p##p(p, [](void* p) { GetHandler()->Release(p); })

void* p = GetHandler()->Create();
GUARD(p); // 安全:会在当前作用域下,创建名为pp的shared_ptr<void>
当然,如果只希望用独占性的管理第三方库的资源,可以用unique_ptr。

// OK
#define GUARD(p) std::unique_ptr<void, void(*)(int*)> p##p(p, [](void* p) { GetHandler()->Release(p); })
小结:
1)使用宏定义方式的优势:即使忘记对智能指针赋值,也能正常运行,安全又方便。
2)使用GUARD这种智能指针管理第三方库的方式,其本质是智能指针,能在各种场景下正确释放内存。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值