什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。
C/C++程序中一般我们关心两种方面的内存泄漏:
- 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分 内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
- 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放 掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
内存泄漏很常见,解决方案通常分为两种:
场景:main函数里调用func函数,在func函数里申请了一个int大小的空间,然后调用div函数,在div函数进行除法操作,若出现除零,则直接抛异常,直接跳转到main函数捕获异常,从而没有释放掉在func函数内申请的资源,即内存泄漏。
#include
#include
using namespace std;
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
int* ptr = new int;//new了一个int大小的资源
cout << div() << endl;
delete ptr;//因为抛异常导致这个int资源没有被正常释放
cout << "delete ptr" << endl;
}
int main()
{
try
{
func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
解决方法一:在func函数内捕获一次异常,进行对申请资源的释放后,再将异常抛出,让外层栈帧去捕获解决异常。
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{ int* ptr = new int;//new了一个int大小的资源
try {
cout << div() << endl;
}
catch (...)
{
delete ptr;//因为抛异常导致这个int资源没有被正常释放
cout << "delete ptr" << endl;
throw;
}
}
int main()
{
try
{
func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
解决方案二:让智能指针对资源进行管理,调用智能指针的构造函数申请资源,调用智能指针的析构函数释放资源。
template<class T>
class smartptr
{
public:
smartptr(T* ptr=nullptr) :_ptr(ptr)
{}
~smartptr()
{
if (_ptr)
{
cout << "delete _ptr" << endl;
delete _ptr;
}
}
// 像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
smartptr<int> sma(new int);
cout << div() << endl;
}
int main()
{
try
{
func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
*
和->
运算符进行重载。RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
需要注意的是:
*
和->
运算符进行重载,使得对象具有像指针一样的行为。int main()
{
smartptr<int> sm1(new int);
smartptr<int> sm2(sm1);//拷贝构造
smartptr<int> sm3(new int);//赋值重载
sm3 = sm1;
return 0;
}
智能指针要模拟出原生指针的行为,而我们将一个指针赋值給另一个指针,其目的就是让两个指针对同一份资源进行管理,但单纯的浅拷贝会导致空间多次释放,因此根据实现的场景不同,衍生出不同的智能指针。
auto_ptr的实现目的:对资源的管理权进行转移
auto_ptr是C++98中引入的智能指针,auto_ptr通过管理权转移的方式解决智能指针的拷贝问题,保证一个资源在任何时刻都只有一个对象在对其进行管理,这时同一个资源就不会被多次释放了。
auto_ptr的模拟实现
template<class T>
class Auto_ptr
{
public:
Auto_ptr(T* ptr=nullptr):_ptr(ptr){}
~Auto_ptr() { cout << "delete _ptr" << endl; delete _ptr; }
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
Auto_ptr(Auto_ptr<T>& tp)//拷贝构造
:_ptr(tp._ptr)
{
tp._ptr = nullptr;//悬空
}
Auto_ptr& operator=(Auto_ptr<T>& tp)
{
if (this!=&tp)//判断是否是自己赋值給自己
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
_ptr = tp._ptr;//转移资源管理权
tp._ptr = nullptr;//悬空
}
return *this;
}
private:
T* _ptr;
};
简易auto_ptr实现思路:
*
和->
运算符进行重载,使得对象具有像指针一样的行为。测试代码
int main()
{
Auto_ptr<int> apt1(new int(1));
*apt1 = 10;
Auto_ptr<int> apt2(apt1);
Auto_ptr<int> apt3(new int(3));
apt3 = apt2;
return 0;
}
unique_ptr的实现目的:防止拷贝
通过防止在智能指针之间互相拷贝,暴力的解决了多个智能指针对同一块资源进行释放的问题。
template<class T>
class Unique_ptr
{
public:
Unique_ptr(T* ptr):_ptr(ptr){}
~Unique_ptr()
{
if (_ptr)
{
cout << "delete _ptr" << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
Unique_ptr<T>& operator=(const Unique_ptr<T>& upt) = delete;//禁用拷贝构造
Unique_ptr(const Unique_ptr<T>& upt) = delete;//禁用拷贝赋值
private:
T* _ptr;
};
int main()
{
Unique_ptr<int> upt1(new int(1));
cout << *(upt1) << endl;
Unique_ptr<int> upt2(upt1);
Unique_ptr<int> upt3(new int(3));
upt3 = upt1;
return 0;
}
简易unique_ptr实现思路:
*
和->
运算符进行重载,使得对象具有像指针一样的行为。=delete
,防止外部调用。share_ptr的实现目的:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
namespace s
{
template<class T>
class Share_ptr
{
public:
Share_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)) {}
Share_ptr<T>& operator=(const Share_ptr<T>& spt)//赋值重载
{
if (_ptr != spt._ptr)
{
Release();
_ptr = spt._ptr;
_pcount = spt._pcount;
Addpcount();
}
return *this;
}
int use_count()
{
return *_pcount;
}
void Addpcount()
{
(*_pcount)++;
}
Share_ptr(const Share_ptr<T>& spt)//拷贝构造
:_ptr(spt._ptr)
, _pcount(spt._pcount)
{
Addpcount();
}
void Release()
{
if (--(*_pcount) == 0 && _ptr)
{
//计数为0,释放资源
cout << "delete _ptr" << endl;
delete _ptr;
delete _pcount;
}
}
~Share_ptr()
{
Release();
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
int* _pcount;
};
}
int main()
{
s::Share_ptr<int> spt1(new int(1));
cout << "spt1 pcount: " << spt1.use_count() << endl;
s::Share_ptr<int> spt2(spt1);
cout << "spt2 pcount: " << spt2.use_count() << endl;
s::Share_ptr<int> spt3(new int(3));
spt3 = spt1;
cout << "spt3 pcount: " << spt3.use_count() << endl;
return 0;
}
简易auto_ptr实现思路:
++
。--
_pcount,然后与传入对象一起管理传入对象所管理的资源,即++
_pcount。*
和->
运算符进行重载,使得对象具有像指针一样的行为。为什么share_ptr的引用计数要放在堆上?
std::shared_ptr的线程安全问题
首先验证C++库里的shared_ptr
#include
#include
#include
#include
#include
using namespace std;
struct Date
{
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
void test_shared_ptr1()
{
int n = 50000;
mutex mtx;
std::shared_ptr<Date> sp1(new Date);
thread t1([&]()
{
for (int i = 0; i < n; ++i)
{
std::shared_ptr<Date> sp2(sp1);
//mtx.lock();
sp2->_year++;
sp2->_day++;
sp2->_month++;
// mtx.unlock();
}
});
thread t2([&]()
{
for (int i = 0; i < n; ++i)
{
std::shared_ptr<Date> sp3(sp1);
// mtx.lock();
sp3->_year++;
sp3->_day++;
sp3->_month++;
// mtx.unlock();
}
});
t1.join();
t2.join();
cout << sp1.use_count() << endl;
cout << sp1.get() << endl;
cout << sp1->_year << endl;
cout << sp1->_month << endl;
cout << sp1->_day << endl;
}
int main()
{
test_shared_ptr1();
}
struct Date
{
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
void test_shared_ptr1()
{
int n = 500;
mutex mtx;
std::shared_ptr<Date> sp1(new Date);
thread t1([&]()
{
for (int i = 0; i < n; ++i)
{
std::shared_ptr<Date> sp2(sp1);
mtx.lock();
sp2->_year++;
sp2->_day++;
sp2->_month++;
mtx.unlock();
}
});
thread t2([&]()
{
for (int i = 0; i < n; ++i)
{
std::shared_ptr<Date> sp3(sp1);
mtx.lock();
sp3->_year++;
sp3->_day++;
sp3->_month++;
mtx.unlock();
}
});
t1.join();
t2.join();
cout << sp1.use_count() << endl;
cout << sp1.get() << endl;
cout <<"sp1->_year: " << sp1->_year << endl;
cout <<"sp1->_month: " << sp1->_month << endl;
cout <<"sp1->_year: " << sp1->_day << endl;
}
int main()
{
test_shared_ptr1();
}
加互斥锁解决shared_ptr的线程安全问题
要解决引用计数的线程安全问题,本质就是让对引用计数的自增和自减变成一个原子操作,因此对引用计数的操作进行加锁保护,将对引用计数的操作划分为临界区,每次只允许一个线程进入临界区做引用计数操作。
namespace s
{
template<class T>
class Share_ptr
{
public:
Share_ptr(T* ptr = nullptr) :_ptr(ptr), _pcount(new int(1)),_mut(new mutex) {}
Share_ptr<T>& operator=(const Share_ptr<T>& spt)//赋值重载
{
if (_ptr != spt._ptr)
{
Release();
_ptr = spt._ptr;
_pcount = spt._pcount;
_mut = spt._mut;
Addpcount();
}
return *this;
}
int use_count()
{
return *_pcount;
}
void Addpcount()
{
_mut->lock();
(*_pcount)++;
_mut->unlock();
}
Share_ptr(const Share_ptr<T>& spt)//拷贝构造
:_ptr(spt._ptr)
, _pcount(spt._pcount)
,_mut(spt._mut)
{
Addpcount();
}
void Release()
{
bool flag = false;
_mut->lock();
if (--(*_pcount) == 0 && _ptr)
{
//计数为0,释放资源
cout << "delete _ptr" << endl;
delete _ptr;
delete _pcount;
flag = true;
}
_mut->unlock();
if (flag == true)
{
delete _mut;
}
}
~Share_ptr()
{
Release();
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
int* _pcount;
mutex* _mut;
};
}
shared_ptr的循环引用问题
struct ListNode
{
int _data=0;
s::Share_ptr<ListNode> _prev;
s::Share_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
s::Share_ptr<ListNode> node1(new ListNode);
s::Share_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
weak_ptr的作用为:
- 构造出来的weak_ptr对象与shared_ptr对象管理同一份资源,但不会增加这块资源对应的引用计数。
- weak_ptr支持用shared_ptr对象来构造weak_ptr对象
- weak_ptr不是用来管理资源释放的,它主要是用来解决shared_ptr的循环引用问题。
namespace t
{template<class T>
class weak_ptr
{
public:
weak_ptr() :_ptr(nullptr) {}
weak_ptr<T>& operator=( s::Share_ptr<T>& spt)//赋值重载
{
_ptr = spt.get();
return *this;
}
weak_ptr(const s::Share_ptr<T>& spt)//拷贝构造
:_ptr(spt.get())
{}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
};
}
*
和->
运算符进行重载,使得对象具有像指针一样的行为。struct ListNode
{
int _data = 0;
t::weak_ptr<ListNode> _prev;
t::weak_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
s::Share_ptr<ListNode> node1(new ListNode);
s::Share_ptr<ListNode> node2(new ListNode);
cout << " node1.use_count: " << node1.use_count() << endl;
cout << " node2.use_count: " << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << " node1.use_count: " << node1.use_count() << endl;
cout << " node2.use_count: " << node2.use_count() << endl;
return 0;
}
可以看到此时资源正常释放了。
当智能指针的生命周期结束,所有的智能指针的析构函数都默认以
delete
的方式将资源释放。实际上这种方式并不好,因为不是所有智能指针所管理的资源都是以new
的方式创建的。比如智能指针可能管理以new[]
方式创建的资源,也管理的是一个文件指针。
int main()
{
s::Share_ptr<int> sp(new int[10]);
s::Share_ptr<int> sp1(fopen("test.cpp", "r"));
return 0;
}
new[]
方式创建的资源进行delete会造成程序崩溃,因为以new[]
申请到的资源必须以delete[]
的方式释放。而以fopen
打开的文件指针必须以fclose
的方式进行关闭。定制删除器的实现问题
- C++标准库中实现shared_ptr时是分成了很多个类的,因此C++标准库中可以将删除器的类型设置为构造函数的模板参数,然后将删除器的类型在各个类之间进行传递。
- 我们是直接用一个类来实现share_ptr,因此不能将定制删除器的类型设置为构造函数的模板参数。删除器需要在析构函数的Release函数中使用,因此需要用一个成员变量将器删除器保存下来,而在定义这个成员变量时就需要指定删除器的类型,因此就需要給shared_ptr类再增加一个模板参数,在构造shared_ptr对象时就指定删除器的类型。然后增加一个支持传入删除器的构造函数,在构造shared_ptr对象时就传递删除器,在释放资源时就调用该删除器即可。设置一个默认删除器,当用户定义shared_ptr对象时不传入删除器,就默认以
delete
的方式释放资源。
template<class T>
class default_delete
{
public:
void operator()(T* ptr)
{
cout << "delete ptr" << endl;
delete ptr;
}
};
template<class T>
class Delarry
{
public:
void operator()( T* arr)
{
cout << "delete[]" << arr << endl;
delete[]arr;
}
};
template<class T,class D=default_delete<T>>
class Share_ptr
{
public:
Share_ptr(T* ptr ,D del) :_ptr(ptr), _pcount(new int(1)),_mut(new mutex),_del(del) {}
void Release()
{
bool flag = false;
_mut->lock();
if (--(*_pcount) == 0 && _ptr)
{
//计数为0,释放资源
cout << "delete _ptr" << endl;
_del( _ptr);//调用对应制定删除器释放资源
delete _pcount;
flag = true;
}
_mut->unlock();
if (flag == true)
{
delete _mut;
}
}
~Share_ptr()
{
Release();
}
//......
private:
T* _ptr;
int* _pcount;
mutex* _mut;
D _del;
class Fclose
{
public:
void operator()(FILE* ptr)
{
cout << "fclose ptr" << endl;
fclose(ptr);
}
};
int main()
{
s::Share_ptr<int,s::Delarry<int>> sp(new int[10],s::Delarry<int>());
s::Share_ptr<FILE, function<void(FILE*)>> sp2(fopen("test.cpp", "r"), [](FILE* ptr) {
cout << "fclose: " << ptr << endl;
fclose(ptr); });
return 0;
}