目录
内存泄漏问题初窥
#include <iostream>
#include <stdexcept>
int div() {
int a, b;
std::cin >> a >> b;
if (b == 0) {
throw std::invalid_argument("除0错误");
}
return a / b;
}
void Func() {
int* p1 = new int;
int* p2 = new int;
std::cout << div() << std::endl;
delete p1;
delete p2;
}
int main() {
try {
Func();
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
return 0;
}
Func 函数中, new 操作或 div 函数抛异常,会使 p1 、 p2 指向内存无法释放,造成内存泄漏。
内存泄漏详解
定义与危害
内存泄漏指程序分配内存后,因疏忽或错误未释放不再使用的内存,造成内存浪费。长期运行程序出现内存泄漏,会致响应变慢、最终卡死。
分类
- 堆内存泄漏:用 malloc / new 等分配堆内存,若未用 free / delete 释放,如 int* p = new int; 后无 delete p; ,会产生堆内存泄漏。
- 系统资源泄漏:程序使用套接字、文件描述符等系统资源,未正确释放,会造成系统资源浪费,影响系统性能。
检测方法
- Linux: valgrind 可检测内存泄漏,在终端运行 valgrind your_program 分析。
- Windows: VLD 集成到Visual Studio 可检测内存泄漏,调试时自动报告。
避免策略
- 遵循良好编码规范,但遇异常仍可能泄漏。
- 利用RAII思想,如智能指针:
- unique_ptr :独占资源,离开作用域自动释放, std::unique_ptr<int> ptr(new int(10)); 。
- shared_ptr :共享资源,引用计数为0时释放, std::shared_ptr<int> ptr1(new int(20)); 。
智能指针是C++ 避免内存泄漏的有效工具,合理使用能提升程序内存管理的安全性与可靠性。
auto_ptr
源码
#pragma once
#include<iostream>
using namespace std;
namespace ldg
{
template<class T>
class auto_ptr
{
public:
// RAII
// 像指针一样
auto_ptr(T* ptr)
:_ptr(ptr)
{
}
~auto_ptr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// ap3(ap1)
// 管理权转移
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
private:
T* _ptr;
};
缺陷:拷贝时使指针悬空
unque_ptr
解决了悬空问题,暴力阻止拷贝
源码
template<class T>
class unique_ptr
{
public:
// RAII
// 像指针一样
unique_ptr(T* ptr)
:_ptr(ptr)
{
}
~unique_ptr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// ap3(ap1)
// 管理权转移
// 防拷贝
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
private:
T* _ptr;
};
对拷贝构造和=重载只声明,同时私有化防止外部定义实现了解决了悬空问题
shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
源码
template<class T>
class shared_ptr
{
public:
// RAII
// 像指针一样
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1))
{
}
~shared_ptr()
{
if (--(*_pcount) == 0)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pcount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// sp3(sp1)
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
++(*_pcount);
}
// sp1 = sp5
// sp6 = sp6
// sp4 = sp5
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr == sp._ptr)
return *this;
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
int use_count() const
{
return *_pcount;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
};
std::shared_ptr的线程安全问题
通过下面的程序我们来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分
为两方面:
1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、--是需要加锁的,也就是说引用计数的操作是线程安全的。
2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
循环操作缺陷
示例代码
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << this;
cout << " ~A()" << endl;
}
//private:
int _a;
};
struct Node
{
A _val;
//Node* _next;
//Node* _prev;
//bit::shared_ptr<Node> _next;
//bit::shared_ptr<Node> _prev;
ldg::weak_ptr<Node> _next;
ldg::weak_ptr<Node> _prev;
// weak_ptr不是RAII智能指针,专门用来解决shared_ptr循环引用问题
// weak_ptr不增加引用计数,可以访问资源,不参与资源释放的管理
};
//
//int main()
//{
//
//
// // 循环引用
// ldg::shared_ptr<Node> sp1(new Node);
// ldg::shared_ptr<Node> sp2(new Node);
//
// cout << sp1.use_count() << endl;
// cout << sp2.use_count() << endl;
//
// sp1->_next = sp2;
// sp2->_prev = sp1;
//
// cout << sp1.use_count() << endl;
// cout << sp2.use_count() << endl;
//
// return 0;
//}
解决循环操作缺陷需要weak_ptr;
weak_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;
};
}
weak_ptr 是C++ 智能指针的一种,其主要作用是解决 shared_ptr 带来的循环引用问题,同时还能用于观察 shared_ptr 管理的对象。以下是具体介绍:
解决循环引用
当两个或多个 shared_ptr 相互引用时,会导致循环引用,使对象无法被正确释放,造成内存泄漏。 weak_ptr 可以打破这种循环引用。例如,有两个类 A 和 B , A 中有一个 shared_ptr 指向 B , B 中也有一个 shared_ptr 指向 A ,形成循环引用。若将 B 中指向 A 的指针改为 weak_ptr ,则可避免循环引用,使对象在不再被其他 shared_ptr 引用时能正常释放内存。
观察 shared_ptr 管理的对象
weak_ptr 可以观察 shared_ptr 所管理的对象,它不影响对象的引用计数。可以通过 lock 函数尝试获取 weak_ptr 所观察对象的 shared_ptr ,如果对象还存在, lock 函数会返回一个有效的 shared_ptr ,否则返回一个空的 shared_ptr 。这在某些需要检查对象是否存在,但又不想增加对象引用计数的场景中很有用。