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;
}