目录
一、内存泄漏
1、什么是内存泄漏
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而
造成了内存的浪费。
2、内存泄漏的危害
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会
导致响应越来越慢,最终卡死。
3、内存泄漏分类
3.1 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过
malloc / calloc / realloc / new
等从堆中分配的一块内存,用完后必须通过调用相应的 free
或者
delete
删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak
。
3.2 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
4、如何避免内存泄漏
1.
工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
2. 采用RAII思想或者智能指针来管理资源。
3.
有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4.
出问题了使用内存泄漏工具检测。
5. 内存泄漏非常常见,解决方案分为两种:
1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。
二 智能指针
1、RAII
RAII
(
Resource Acquisition Is Initialization
)是一种
利用对象生命周期来控制程序资源
(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源
,接着控制对资源的访问使之在对象的生命周期内始终保持有效,
最后在对象析构的
时候释放资源
。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有
两大好处
:①不需要显式地释放资源。 ②采用这种方式,对象所需的资源在其生命期内始终保持有效。
2、智能指针的原理
1. RAII
特性
2.
重载
operator*
和
opertaor->
,具有像指针一样的行为。
智能指针两大基本特性
1、RAII
2、可以像指针一样* -> 去使用
template<class T>
class SmartPtr
{
public:
//RAII
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
cout << "delete" << endl;
delete _ptr;
}
//可以像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int div() throw(invalid_argument)
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void f1()
{
//int* p = new int;
将p托管,防止内存泄漏,无论抛异常还是不抛异常,都能正常释放内存
//SmartPtr<int> pt(p);
SmartPtr<int> sp(new int);
*sp = 100;
SmartPtr<pair<string, int>> sp2(new pair<string, int>("sort", 1));
sp2->first;
sp2->second;
cout << div() << endl;
//delete p;
}
int main()
{
try
{
f1();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
3、auto_ptr
auto_ ptr C+ +98中的->拷贝时,管理权转移,保持- 个资源只有一一个对象管理,同时导致拷贝后原对象悬空。(不推荐使用,甚至禁止使用)
//C++98 auto_ptr 拷贝(管理权转移)
namespace zsd
{
template<class T>
class auto_ptr
{
public:
//RAII
auto_ptr(T* ptr)
:_ptr(ptr)
{}
~auto_ptr()
{
if (_ptr)
{
cout << "delete" << endl;
delete _ptr;
_ptr = nullptr;
}
}
//管理权转移,保证只有一个对象在管理资源
auto_ptr(auto_ptr<int>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
//可以像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
4、unique_ptr
C++11
中开始提供更靠谱的
unique_ptr
unique_ptr
的实现原理:简单粗暴的防拷贝
namespace zsd
{
//C++98 boost C++11
//C++11 第一个智能指针,unique_ptr,解决拷贝问题
//简单粗暴:直接禁止拷贝
template<class T>
class unique_ptr
{
public:
//RAII
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
cout << "delete" << endl;
delete _ptr;
}
//可以像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
private:
T* _ptr;
};
}
5、shared_ptr
C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
shared_ _ptr C++11 -> 就是boost中shared _ptr. 允许拷贝,使用引用计数的方式允许多个智能指针对象管理一个资源。 -- 缺陷:循环引用
5.1 shared_ptr的原理
通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
5.2 shared_ptr的线程安全问题
1.
智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时
++
或
--
,这个操作不是原子的,引用计数原来是1
,
++
了两次,可能还是
2.
这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++
、
--
是需要加锁的,也就是说引用计数的操作是线程安全的。
2.
智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
namespace zsd
{
//C++11 第二个智能指针,shared_ptr,解决拷贝问题
//引用计数
template<class T>
class shared_ptr
{
private:
//线程安全
void AddRef()
{
_pmutex.lock();
++* _pcount;
_pmutex.unlock();
}
void ReleaseRef()
{
_pmutex.lock();
bool flag = false;
if (--(*_pcount) == 0)
{
cout << "delete" << endl;
delete _ptr;
delete _pcount;
flag = true;
}
_pmutex.unlock();
if(flag)
delete _pmutex;
}
public:
//RAII
shared_ptr(T* ptr)
:_ptr(ptr)
, _pcount(new int(1))
, _pmutex(new mutex)
{}
~shared_ptr()
{
ReleaseRef();
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
,_pmutex(sp._pmutex)
{
AddRef();
}
//可以像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
ReleaseRef();
_pcount = sp._pcount;
_ptr = sp._ptr;
_pmutex = sp._pmutex;
AddRef();
}
return *this;
}
private:
T* _ptr;
int* _pcount;
mutex _pmutex;
};
}
6、weak_ptr
不参与资源管理,不增加shared_ptr管理资源的引用计数,可以像指针一样使用,解决循环引用的问题,需要配合多个其他类来一起使用
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
// 可以像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};