开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第N天,点击查看活动详情 2.3 如何检测内存泄漏(了解) 在linux下内存泄漏检测:linux下几款内存泄漏检测工具 在windows下使用第三方工具:VLD工具说明 其他工具:内存泄漏工具比较 🌹2.4 如何避免内存泄漏 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。 采用RAII思想或者智能指针来管理资源。 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。 总结一下: 内存泄漏非常常见,解决方案分为两种:
事前预防型。如智能指针等。 事后查错型。如泄漏检测工具。 💐3. 智能指针的使用及原理 🌹3.1 RAII RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
不需要显式地释放资源。 采用这种方式,对象所需的资源在其生命期内始终保持有效 我们可以借助RALL思想来写一个简单的 智能指针:
#include using namespace std;
template class SmartPtr { public: SmartPtr(T* ptr =nullptr) :_ptr(ptr) {} ~SmartPtr() { if (_ptr)delete _ptr; cout<<"~SmartPtr"<<endl; } private: T* _ptr; };
int main() { int* a = new int(1); SmartPtr sp(a); //将a 指针委托给sp对象管理
SmartPtr<int>sp2(new int(2)); //直接船舰匿名对象给sp2管理
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 🌹3.2 智能指针的原理 上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:AutoPtr模板类中还得需要将 * 、->重载下,才可让其像指针一样去使用。
template class SmartPtr { public: SmartPtr(T* ptr = nullptr) : _ptr(ptr) {} ~SmartPtr() { if(_ptr) delete _ptr; }
T& operator*() {return _ptr;} T operator->() {return _ptr;}
private: T* _ptr; };
struct Date { int _year; int _month; int _day; };
int main() { SmartPtr sp1(new int); *sp1 = 10 cout<<*sp1<<endl; SmartPtr sparray(new Date);
// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 总结一下智能指针的原理:
RAII特性 重载operator*和opertaor->,具有像指针一样的行为。 🌹3.3 auto_ptr 在C++98版本的库种,提供了 auto_ptr 的智能指针:
auto_ptr文档介绍
我们使用一下std::auto_ptr:
template class auto_ptr { public: auto_ptr(T* p) :_ptr(p) {}
auto_ptr(auto_ptr<T>& ap)
: _ptr(ap._ptr)
{
ap._ptr = nullptr;
}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
private: T* _ptr; };
void test_auto_ptr() { auto_ptr ap1(new int); auto_ptr ap2(ap1); auto_ptr ap3(ap2); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 auto_ptr的实现原理:管理权转移的思想,拷贝时会导致原对象悬空
一般的公司都有明确的条文规定不可以使用auto_ptr,因为这真的是一个很不负责任的设计
🌹3.4 unique_ptr uni 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 🌹3.5 shared_ptr 但也总不能不拷贝呀,所以unique_ptr也不会一个好的方式呀,所以引入了shared_ptr
shared_ptr 是当前最为广泛使用的智能指针,它可以安全的提供拷贝操作。
shared_ptr的文档介绍
那么shared_ptr的原理是什么?
我们可以对一个资源添加一个计数器,让所有管理该资源的智能共用这个计数器,倘若发生拷贝,计数器加一,倘若有析构发生, 计数器减一,当计数器等于0的时候,就把对象析构掉。
再具体一点:
shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源; 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
//shared_ptr的模拟实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 线程安全问题
智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁(但是引用计数是在堆上开辟的资源,所以为了能够确保使用同一把锁,锁资源也应该在堆上进行开辟)的,也就是说引用计数的操作是线程安全的。 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。