C/C++动态内存管理,智能指针及RAlI思想。

一、C 语言动态内存管理(malloc/calloc/realloc/free)

四个核心函数

都在 <cstdlib>,操作原始堆内存 ,只分配 / 释放内存,不会调用构造、析构函数

1、void* malloc(size_t size)

分配 size 字节未初始化堆内存;分配失败返回 NULL。

复制代码
int* arr = (int*)malloc(sizeof(int) * 10);

2、void* calloc(size_t num, size_t size)

分配 num 个、每个 size 字节,分配后内存全部置 0

3、void* realloc(void* ptr, size_t new_size)扩容 / 缩容已有内存;

ptr 为 NULL:等价 malloc;

new_size=0:等价 free;原内存数据会拷贝到新空间,旧内存自动释放。

4、void free(void* ptr)释放堆内存,只能释放 malloc/calloc/realloc 分配的内存;

free 空指针安全;

重复 free、free 栈变量 / 全局变量 → 未定义行为(崩溃)。

二、C++ 原生动态内存 new /delete

1. 基础用法

复制代码
// 单个对象
int* p = new int(10);
delete p;

// 对象数组
int* arr = new int[5]{1,2,3,4,5};
delete[] arr; // 数组必须带[],否则只调用第一个元素析构,内存错乱

2. new 底层两步

  1. operator new:底层调用 malloc 开辟堆内存;
  2. 调用构造函数初始化对象。

3. delete 底层两步

  1. 调用对象析构函数,释放对象内部资源;
  2. operator delete 调用 free 归还堆内存。

4. new/delete 相比 malloc/free 的优势

  1. 自动调用构造、析构,适配 C++ 类、自定义对象;
  2. 自带类型安全,无需强制类型转换;
  3. 支持数组语法,区分单个对象与数组释放。

5. new/delete 存在大量问题

  1. 忘记 delete → 内存泄漏;
  2. 同一块内存多次 delete / 栈指针 delete → 程序崩溃;
  3. 函数中途抛异常、提前 return,delete 执行不到;
  4. 多函数传递裸指针,内存所有权模糊,没人负责释放;
  5. 异常安全差:new 成功后,如果后续代码抛异常,delete 不会执行。

根本解决方案:RAII + 智能指针

三、RAII 核心思想(Resource Acquisition Is Initialization 资源获取即初始化)

1. 核心定义

资源获取 写在类的构造函数资源释放 写在类的析构函数 ;利用栈对象自动生命周期:栈上局部对象离开作用域(正常 return、break、抛出异常)时,编译器一定会自动调用析构函数,保证资源释放。

四、C++ 标准智能指针(<memory>,全部基于 RAII 实现)

智能指针本质是封装裸指针的栈对象 ,依靠 RAII 自动释放堆内存,彻底替代手动 delete。分三类:unique_ptrshared_ptrweak_ptr

1. std::unique_ptr 独占智能指针(最常用)

特性
  1. 独占所有权:一块内存只能被一个 unique_ptr 持有;
  2. 禁止拷贝构造 / 拷贝赋值 ,只支持移动语义 std::move
  3. 无引用计数,运行时开销几乎和裸指针一致,性能最优;
代码示例
复制代码
// 推荐,一步分配内存,异常安全
auto up = std::make_unique<int>(666);
// auto up2 = up;  // 编译报错,禁止拷贝
auto up2 = std::move(up); // 所有权转移,up变为空
// 离开作用域自动释放内存
适用场景

资源只需要单一管理者,绝大多数堆内存场景优先 unique_ptr。

.最常用成员函数

  1. get():获取裸指针
  2. reset():释放 / 更换资源
  3. release():交出裸指针,自身置空

2. std::shared_ptr 共享智能指针

特性
  1. 共享所有权 :内部维护原子引用计数器
  2. 支持拷贝:拷贝时引用计数 + 1;智能指针销毁 / 覆盖赋值时计数 - 1;
  3. 引用计数变为 0 时,自动释放堆内存;
  4. 性能损耗:原子操作多线程同步,开销高于 unique_ptr;
  5. 致命缺陷:循环引用会导致内存泄漏
代码示例
复制代码
auto sp1 = std::make_shared<std::string>("test");
auto sp2 = sp1; // 计数+1,两个指针共用一块内存

.最常用成员函数

  • get():获取裸指针
  • reset():释放 / 更换资源
  • use_count():获取当前引用计数
循环引用泄漏场景(面试高频)

两个类互相持有对方 shared_ptr,引用计数永远不为 0,内存无法释放:

复制代码
struct A {
    std::shared_ptr<B> b;
};
struct B {
    std::shared_ptr<A> a;
};

void leakDemo() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b = b;
    b->a = a;
    // 函数结束,a、b销毁,但互相持有计数,内存泄漏
}

3. std::weak_ptr 弱智能指针(配套 shared_ptr 解决循环引用)

特性
  1. 不增加引用计数,不拥有内存所有权,仅做 "观察者";
  2. 只能由 shared_ptr 构造;
  3. 使用前调用 .lock() 升级为 shared_ptr,判断内存是否存活(.expired())。
改造上面循环引用,解决泄漏
复制代码
struct A {
    std::shared_ptr<B> b;
};
struct B {
    std::weak_ptr<A> a; // 改用weak_ptr,不增加计数
};

常用成员函数

  • lock():升级成 shared_ptr(用来访问对象)
  • expired():判断对象是否已经销毁
  • reset():清空弱指针
  • use_count():查看强引用数量

4. make_unique /make_shared 为什么比 new 好?

  1. 内存分配一步到位:make_shared 把控制块 + 对象内存分配在同一块连续堆,减少内存碎片,缓存友好;
  2. 异常安全 :若 func(std::shared_ptr<T>(new T), func_throw()),编译器可能先执行 new T 分配内存,再执行抛异常函数,内存分配成功但无智能指针接管,直接泄漏;make 系列将分配和构造绑定,不会出现裸内存悬空。
相关推荐
一杯奶茶¥1 小时前
c语言程序设计大学期末考试考研知识点重点笔记题库复习资料C语言重点考点题库资料合集
c语言·笔记·考研·c语言程序设计·c语言笔记
hold?fish:palm2 小时前
从源码到可执行文件:C++程序的编译过程
开发语言·c++
沫离痕2 小时前
Cassandra 4.0.11 Docker 安装与配置手册
java·spring boot
lingran__2 小时前
C++_STL简介
开发语言·c++
plainGeekDev2 小时前
libs 目录 → Gradle 依赖管理
android·java·kotlin
南国韭菜2 小时前
【无标题】
c++·qt
CodeStride2 小时前
就像长跑配速一样,我是如何平衡代码性能与可读性的
java
弹简特3 小时前
【Java项目-企悦抽】07-用户与认证模块-实现用户注册01
java·开发语言
L歪歪君3 小时前
Apache Doris全链路性能优化实战指南:从架构设计到生产落地
java·开发语言·算法