内存

chapter 8.pdf

栈内存 V.S. 堆内存

– 栈内存的特点:更好的局部性,对象自动销毁
– 堆内存的特点:运行期动态扩展,需要显式释放

  • 内存的使用 ```cpp int fun() { //正确使用 int res = new int[2]; return res; //错误使用:这里返回了临时对象,x在栈内存中,随着栈帧的自动销毁而销毁 int x = 2; return &x; }

int main() { int y = fun(); std::cout << y <<std::endl; //…. delete y; }

  1. - C++ 中通常使用 new delete 来构造、销毁对象
  2. - 对象的构造分成两步:分配内存与在所分配的内存上构造对象;对象的销毁与之类似
  3. - 构造:
  4. - new 分配内存
  5. - 赋值2即构造对象
  6. - 销毁:
  7. - 销毁对象->类的析构函数
  8. - 归还内存给系统
  9. <a name="wsR8I"></a>
  10. #### new 的几种常见形式
  11. - 构造单一对象 / 对象数组
  12. - int *y = new int[2]; //1.分配内存,2个int(连续的2*4个字节) 2.缺省初始化
  13. - int *y = new int; //1.分配内存,2.缺省初始化
  14. - int *y = new int[5]{1,2,3,4,5};// 列表初始化
  15. - int *y = new int[5]{}; //列表初始化为0;
  16. - nothrow new
  17. - int *y = new(std::nothrow) int[5]{};
  18. - 分配不成功的时候y指向nullptr;
  19. - 不加nothrow,分配不成功的时候跳转到异常抛出程序
  20. - placement new
  21. - 使用堆上现有的内存
  22. ```cpp
  23. char ch[sizeof(int)];
  24. int* y = new(ch) int(4); //把ch地址赋值给y,注意ch内存大小要大于y的内存
  • – new auto
    • int *y = new auto(3); //正确,根据3推导出int类型
    • int *y = new auto; //错误,auto无法适应等号前面的int

new 与对象对齐

  • 可以确保对齐信息 ```cpp struct alignas(256) Str{}; //alignas:对齐,Str的地址为256的倍数,即最后两位为00 int main() { Str* ptr = new Str();

}

  1. <a name="MUkSp"></a>
  2. #### delete 的常见用法
  3. – 销毁单一对象 / 对象数组
  4. - delete ptr; //销毁单一对象
  5. - delete []ptr; //销毁数组
  6. – placement delete
  7. - 只销毁对象,不归还内存:类调用析构函数,内建数据类型无操作
  8. <a name="oZhrM"></a>
  9. #### 使用 new 与 delete 的注意事项
  10. – 根据分配的是单一对象还是数组,采用相应的方式销毁<br />– delete nullptr //delete什么都不做
  11. ```cpp
  12. int *x = nullptr;
  13. if(...)
  14. {
  15. x = new int[3];
  16. }
  17. delete x;
  18. //如果进入if则走正常delete流程
  19. //如果不进入if,x为空指针,delete nullptr实际上什么都没做

– 不能 delete 一个非 new 返回的内存
– 同一块内存不能 delete 多次

调整系统自身的 new / delete 行为

  • 不要轻易使用
    • 不建议替换全局的new
  • 类中可以使用operator new,详见cppreference

智能指针

使用 new 与 delete 的问题:内存所有权不清晰,容易产生不销毁,多销毁的情况

  • new和delete不在同一块语句中 ```cpp //所有权不清晰的情况 int fun() { int res = new int[2]; return res; }

int main() { int y = fun(); std::cout << y <<std::endl; //…. delete y; }

 C++ 的解决方案:智能指针<br />– auto_ptr ( C++17 删除)<br />– shared_ptr / uniuqe_ptr / weak_ptr
<a name="FhZMe"></a>
## shared_ptr
基于引用计数的共享内存解决方案<br />– 基本用法
```cpp
share_ptr<int> x(new int(3)); //引用计数,自动销毁
share_ptr<int> y = x; //计数加1

std::shared_ptr<int> fun()
{
    std::shared_ptr<int> res(new int(3));
    return res;
}

int main()
{
    std::shared_ptr<int> x = fun();
}
//安全引用,不用担心少or多销毁的情况,计数为0的时候自动销毁

– reset / get 方法

  • x.get(); //返回int 类型,而非share_ptr,用于兼容int的使用情况
  • x.reset(new int(4)); //删除原始内存指向新的地址
  • x.reset() //删除原始内存,不指向新的地址

– 指定内存回收逻辑

void fun(int *ptr)
{
    delete ptr;
}

int main()
{
    std::shared_ptr<int> x(new int(3),fun);
}
//自定义回收逻辑

//用法2:自定义回收逻辑,可以避免res静态内存被错误回收
void dummy(int*) {}
share_ptr<int> fun(){
    static int res = 3;
    return std::shared_ptr<int>(&res,dummy);
}
int main()
{
    std::shared_ptr<int> x = fun();
}

– std::make_shared

  • std::shared_ptr x(new int(3),fun);
  • shared_ptrx = make_shared(3); //建议使用make_shared,构造传参的方式,可以把计数和内存放在尽量近的位置

– 支持数组( C++17 支持 shared_ptr ; C++20 支持 make_shared 分配数组)
– 注意: shared_ptr 管理的对象不要调用 delete 销毁

int *ptr = new int(3);
std::shared_ptr<int> x(ptr);
std::shared_ptr<int> y(ptr); //错误,会导致重复删除
std::shared_ptr<int> y(x.get()); //错误,会导致重复删除
//直接传入内存地址,不会使计数+1,删除的时候容易导致重复删除

unique_ptr

—— 独占内存的解决方案
– 基本用法

  • unique_ptr y = x; //错误用法
  • unique_ptr y = std::move(x); //正确用法

– unique_ptr 不支持复制,但可以移动
– 为 unique_ptr 指定内存回收逻辑

weak_ptr

—— 防止循环引用而引入的智能指针
– 基于 shared_ptr 构造
– lock 方法

动态内存基础