C++: 内存管理

C/C++内存区域

栈与堆的区别

  • 功能

    • 栈用于存储方法调用的上下文,局部变量和函数参数等

    • 堆用于存储动态分配的数据,如对象、数组等

  • 生命周期

    • 栈存放的局部变量, 出了作用域就自动销毁(编译时确定)

    • 堆存储动态分配的数据, 程序员手动管理

  • 内存大小

    • 栈的内存更小, 一般几MB~几GB

    • 堆的内存更大, 取决于操作系统配置

  • 分配速度

    • 栈的内存分配速度更快(有相关指令与寄存器), 其通过调整栈指针来实现, 分配和释放的操作都再固定的位置上

    • 堆的内存分配速度更慢, 涉及复杂的管理与追踪, 与内存碎片的整体和查找(且访问内存要访问2次指针, 一次拿到内存地址, 再根据地址访问内存)

new与delete

语法
  • new ,new[]与 delete ,delete[]
  • 语法
  • int* p1 = new int(1);//可以不初始化
    int* p2 = new int[10]{1,2,3,4,5,6};
    
    delete p1;
    delete []p2;
实现

operator new 与 operator delete

  • operator new与 operator delete是系统提供的全局函数
  • new在底层上会调用operator new, operator new是对malloc的封装,delete同理
内存分配

1.new的内存分配

简单类型: new简单类型直接调用operator new分配内存;

复杂类型: 先调用operator new分配内存,然后在分配的内存上调用构造函数;

简单类型: new[]计算好大小后调用operator new;

复杂类型: new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小;

2.delete释放内存

简单类型: 调用free函数,释放p指向的内存(假设指p指向new分配的内存)

复杂类型: 先调用析构函数再调用operator delete;

简单类型: delte[]释放p-4指向的内存(前4个字节存数组大小 -- 假设指p指向new分配的内存)

复杂类型: delte[]先调用n次析构, 再释放内存

new,delete与malloc,free的区别

  • 语法上:new是操作符,malloc是函数
  • 使用上:
    • 内存大小: new自动计算内存大小, malloc需要自己计算
    • 返回值:
      • new自动返回正确的类型 , malloc需要自己强转(可能会出错)
      • new申请内存失败抛异常 , malloc申请失败返回NULL(要检查返回值)
      • new可以控制初始化,对自定义类型调用构造函数初始化,delete会调用析构函数, malloc不行

智能指针(内存管理)

功能:自动管理内存的分配和释放(构造对象的时候获取资源,析构的时候释放资源--解决内存泄漏)

  • RAII:利用对象的生命周期来控制资源
  • 像指针一样使用( * , -> )
  • 问题: 拷贝构造引发重复释放同一段空间(指针是浅拷贝)
种类
  • aoto_prt
    • 原理:管理权限的转移, 若发生拷贝将管理的资源交给新对象,自己置为空
    • 问题:容易引发空指针问题(auto_prt在C++11中被弃用)
  • unique_ptr
    • 功能: 一个对象拥有一个资源
    • 实现:封掉拷贝构造,赋值运算符
    • 优点: 实现简单, 效率高
    • 缺点: 无法共享
  • shared_ptr/weak_ptr
    • 功能: 多个对象拥有一个资源
    • 原理:引用计数:每有一个对象管理这个资源,计数++,不管理就计数--,若计数==0就释放
    • 优点: 资源共享
    • 缺点:(1)循环引用(2)线程安全(需要加锁)
  • weak_ptr
    • 功能: 解决循环引用
    • 原理: 只引用不计数
    • 优点: 解决循环引用
    • 缺点: weak_ptr不保证它指向的内存一定是有效的, 使用之前要检查weak_ptr是否为空指针

shared_ptr代码

#include<mutex>
using namespace std;

namespace code
{
	template<class T>
	class shared_ptr
	{
	public:
		//1.RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _pmutex(new mutex)
		{}

		~shared_ptr()
		{
			Release();
		}
		//2.像指针一样使用
		T& operator*() { return *_ptr; }

		T* operator->() { return _ptr; }

		T* get()const { return _ptr; }

		//3.引用计数解决拷贝构造问题
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmutex(sp._pmutex)
		{
			//拷贝 + 计数++
			AddRef();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//不给自己赋值
			if (_ptr != sp._ptr)
			{
				Release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_pmutex = sp._pmutex;

				AddRef();
			}
			return *this;
		}
		void AddRef()
		{
			_pmutex.lock();
			++(*_pcount);
			_pmutex.unlock();
		}

		void Release()
		{
			_pmutex->lock();
			bool DeleteMutex = false;

			if (--(*_pcount) == 0 && _ptr)
			{
				delete _ptr;
				delete _pcount;
				DeleteMutex = true;
			}
			_pmutex->unlock();

			if (DeleteMutex)delete _pmutex;

		}

		//4.weak_ptr解决重复引用问题
		template<class T>
		class weak_ptr
		{
		public:
			weak_ptr()
				:_ptr(nullptr)
			{}

			weak_ptr(const shared_ptr<T>& sp)
				:_ptr(sp.get())
			{}

			T& operator*() { return *_ptr; }

			T* operator->() { return _ptr; }

		private:
			T* _ptr;
		};

	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmutex;
	};
}

shared_prt相关

1.shared_prt的引用计数: 需要我们自己new一个

  • 不能是属于单个对象的成员变量: 需要被所有管理同一份资源的对象看到
  • 不能是静态成员变量: 每一份资源都应该有自己的计数(可能管理多个资源)

2.循环引用(weak_ptr解决)

因为链接的问题,_next,_pre也参与了资源的管理,导致引用计数++,最后程序结束的引用计数不为0,无法释放资源,导致内存泄漏

使用weak_ptr解决

  • weak_ptr是用来辅助shared_ptr解决循环引用问题的
  • weak_prt不提供引用计数,不参与管理
  • weak_ptr支持像指针一样
使用建议

建议使用make_xx系列, 而不是直接使用new

//unique_ptr
std::unique_ptr<MyClass> ptr2 = std::make_unique<MyClass>();

//shared_ptr
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();

//weak_ptr
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weakPtr = ptr1; // 不增加引用计数

1.可读性

2.性能对比

  • new 分配对象再将其传递给 std::shared_ptr 构造函数(分配两次内存,一次用于对象,一次用于控制块)
  • std::make_shared 通过一次内存分配来创建 std::shared_ptr 和 它管理的对象 (分配一次内存,用于同时存储对象和控制块)

3.安全性

在 new 和 std::shared_ptr 构造之间抛出异常,可能会导致内存泄漏

std::shared_ptr<int> ptr(new int(10)); 存在问题
如果在 new int(10) 之后但在 std::shared_ptr 构造函数之前抛出异常
将无法释放分配的内存, 导致内存泄漏

使用 std::make_shared 可以避免这种问题
因为对象和控制块的创建是原子操作,不会在中途抛出异常

自定义删除器

1.数组管理的时候需要delete[], 而不是delete

std::shared_ptr<char> p(new char[10], [](char* ptr) { delete[] ptr; });

2.自己定义删除方式

#include <iostream>
#include <memory>

//自定义的内存分配和释放函数
void customDeleter(int* ptr) {
    std::cout << "Deleting pointer: " << ptr << std::endl;
    delete ptr;
}

int main() {
    // 使用自定义删除器来记录日志
    std::shared_ptr<int> p(new int(42), customDeleter);

    std::cout << *p << std::endl;

    // p超出作用域时,customDeleter会被调用

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值