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 系列将分配和构造绑定,不会出现裸内存悬空。
相关推荐
萧瑟余晖15 小时前
Java深入解析篇九之NIO详解
java·网络·nio
The Chosen One98515 小时前
高进度算法模板速记(待完善)
java·前端·算法
小羊先生car15 小时前
RTOS-F429-HAL-绝对延时和相对延时(2026/7/31)
c语言·rtos
极光代码工作室17 小时前
基于SpringBoot的课程预约系统
java·springboot·web开发·后端开发
Leighteen18 小时前
`try-finally` 里的 `return`:为什么 `finally` 会悄悄改掉返回值、吞掉异常
java·开发语言
峥无18 小时前
C++11 深度详解:现代 C++ 基石全梳理
开发语言·c++·笔记
阿米亚波18 小时前
【C++ STL】std::unordered_multimap
开发语言·数据结构·c++·笔记·stl
名字还没想好☜19 小时前
Go 的 time.After 在 select 循环里内存泄漏:定时器堆积原理与 timer.Reset 正确姿势
java·数据库·golang·go·goroutine
小王C语言19 小时前
【3. 基于 Vibe Coding 的 OJ 平台】. 构建仓库、环境准备、需求梳理、安装依赖
网络·c++
圆山猫19 小时前
[Virtualization](三):RISC-V H-extension 与 Guest 执行模式
android·java·risc-v