【学习笔记06】内存管理与智能指针学习总结
📝 说明:本文为C++学习笔记,AI辅助系统整理
📅 整理时间:2025年9-10月
🎯 课时:课时05 - 内存管理与智能指针
💡 目的:掌握C++内存管理核心技术和智能指针使用
📚 本文内容
- new/delete vs malloc/free
- 内存泄漏
- unique_ptr
- shared_ptr
- weak_ptr
- make_shared
- RAII
- 野指针和悬挂指针
本篇整理的知识点
课时05学习了C++内存管理的8个核心知识点:
- new/delete与malloc/free的区别
- 内存泄漏识别与预防
- unique_ptr独占指针
- shared_ptr共享指针
- weak_ptr弱引用
- make_shared的优点
- RAII资源管理模式
- 野指针和悬挂指针
知识点1:new/delete vs malloc/free
本质区别
典型示例:
// malloc/free:C语言函数,只分配/释放字节
int* p1 = (int*)malloc(sizeof(int));
*p1 = 42;
free(p1);// new/delete:C++运算符,分配内存+调用构造/析构
int* p2 = new int(42);
delete p2;
对象创建对比
典型示例:
class Student {
public:string name;int age;Student(string n, int a) : name(n), age(a) {cout << "构造函数被调用: " << name << endl;}~Student() {cout << "析构函数被调用: " << name << endl;}
};// new/delete(推荐)
Student* s1 = new Student("张三", 20); // 自动调用构造函数
delete s1; // 自动调用析构函数// malloc/free(危险)
Student* s2 = (Student*)malloc(sizeof(Student));
// s2->name 崩溃!对象未构造
free(s2); // 不会调用析构函数
六大关键区别
特性 | new/delete | malloc/free |
---|---|---|
本质 | C++运算符 | C库函数 |
构造/析构 | ✅ 自动调用 | ❌ 不调用 |
返回类型 | 具体类型指针 | void* 需要强转 |
失败处理 | 抛出bad_alloc | 返回NULL |
大小计算 | 自动计算 | 需要手动sizeof |
重载 | ✅ 可以重载 | ❌ 不能重载 |
常见陷阱
// 陷阱1:混用导致崩溃
int* p = new int(10);
free(p); // ❌ 错误!new配delete// 陷阱2:数组分配释放不匹配
int* arr = new int[10];
delete arr; // ❌ 错误!应该用delete[]
delete[] arr; // ✅ 正确// 陷阱3:malloc分配对象后不构造
Student* s = (Student*)malloc(sizeof(Student));
s->name = "李四"; // ❌ 崩溃!对象未构造
我的理解:C++里必须用new/delete,不要用malloc/free。new不仅分配内存,还会调用构造函数初始化对象。
知识点2:内存泄漏
五种常见泄漏场景
// 场景1:忘记释放
void leak1() {int* p = new int(10);// 忘记delete p;
}// 场景2:异常路径没释放
void leak2() {int* p = new int(10);if (条件) throw exception(); // 异常抛出,delete不会执行delete p;
}// 场景3:容器存指针不释放
vector<int*> vec;
vec.push_back(new int(10)); // 析构时只删vector,不删指针内存// 场景4:循环引用(智能指针)
struct A { shared_ptr<B> b; };
struct B { shared_ptr<A> a; }; // 互相引用,计数永不为0// 场景5:重复赋值前未释放
int* p = new int(10);
p = new int(20); // 前一个内存泄漏
预防方法
// 1. 使用智能指针
unique_ptr<int> p1 = make_unique<int>(42); // 自动释放// 2. RAII原则
class FileGuard {FILE* fp;
public:FileGuard(const char* path) { fp = fopen(path, "r"); }~FileGuard() { if (fp) fclose(fp); } // 自动关闭
};// 3. 工具检测
// Valgrind、AddressSanitizer
知识点3:unique_ptr
核心特性:独占所有权
典型示例:
#include <memory>// 创建
unique_ptr<int> p1 = make_unique<int>(42);// ❌ 不能复制
unique_ptr<int> p2 = p1; // 编译错误// ✅ 可以移动
unique_ptr<int> p3 = std::move(p1); // p1变nullptrcout << *p3 << endl; // 42
// 自动释放
自定义删除器
// 与C接口协作
struct FileCloser {void operator()(FILE* f) const {if (f) fclose(f);}
};using UniqueFile = unique_ptr<FILE, FileCloser>;UniqueFile openFile(const char* path) {return UniqueFile(fopen(path, "rb"));
}
适用场景
- 文件句柄管理
- Socket连接管理
- 独占资源管理
- 工厂函数返回值
知识点4:shared_ptr
引用计数机制
引用计数机制
典型示例:
shared_ptr<int> p1 = make_shared<int>(42);
cout << p1.use_count() << endl; // 1shared_ptr<int> p2 = p1; // 引用计数+1
cout << p1.use_count() << endl; // 2p1.reset(); // 计数-1
cout << p2.use_count() << endl; // 1
// p2离开作用域,计数归0,自动释放
线程安全性
// ✅ 引用计数操作是线程安全的(原子操作)
shared_ptr<int> sp1 = make_shared<int>(42);
shared_ptr<int> sp2 = sp1; // 计数+1,线程安全// ❌ 对象本身的读写不是线程安全的
*sp1 = 100; // 修改对象,不是线程安全!需要加锁
控制块结构
shared_ptr包含两部分:
- 指向对象的指针
- 指向控制块的指针
- 引用计数
- 弱引用计数
- 删除器
- 分配器
知识点5:weak_ptr
解决循环引用
解决方案:用weak_ptr打破循环
循环引用问题
循环引用问题:
struct B;
struct A {shared_ptr<B> b;~A() { cout << "A析构" << endl; }
};
struct B {shared_ptr<A> a; // ❌ 循环引用~B() { cout << "B析构" << endl; }
};shared_ptr<A> pa = make_shared<A>();
shared_ptr<B> pb = make_shared<B>();
pa->b = pb; // A持有B
pb->a = pa; // B持有A,形成环
// 永不析构!
解决方案:
struct B {weak_ptr<A> a; // ✅ 用weak_ptr打破循环~B() { cout << "B析构" << endl; }
};
weak_ptr使用
weak_ptr<int> wp = sp; // 不增加引用计数if (auto p = wp.lock()) { // 转为shared_ptrcout << *p << endl; // 使用对象
}if (wp.expired()) { // 检查对象是否还存在cout << "对象已销毁" << endl;
}
知识点6:make_shared
为什么用make_shared?
原因1:性能优化(一次内存分配)
// 分两次分配
shared_ptr<int> p1(new int(42));
// 第1次:new int(42) 分配对象
// 第2次:shared_ptr分配控制块// 只分配一次
shared_ptr<int> p2 = make_shared<int>(42);
// 对象和控制块一起分配,更快
原因2:异常安全
// ❌ 可能泄漏
func(shared_ptr<int>(new int(42)), other_func());
// 如果other_func()抛异常,new int(42)可能泄漏// ✅ 安全
func(make_shared<int>(42), other_func());
知识点7:RAII
资源获取即初始化
RAII核心思想:
- 构造时获取资源
- 析构时释放资源
- 利用对象生命周期自动管理
典型示例:
class FileHandle {
private:FILE* file;
public:FileHandle(const char* path) {file = fopen(path, "r"); // 构造时打开}~FileHandle() {if (file) fclose(file); // 析构时关闭}
};void process() {FileHandle fh("data.txt");// 使用文件...if (error) {return; // 即使提前返回,也会调用析构关闭文件}
} // 离开作用域,自动关闭
我的理解:RAII让资源管理变得自动化,不需要手动释放,也不怕异常导致泄漏。智能指针就是RAII的典型应用。
知识点8:野指针和悬挂指针
野指针
未初始化的指针,指向随机地址。
int* p; // 野指针,未初始化
*p = 42; // 崩溃!随机地址
解决:
int* p = nullptr; // 安全
悬挂指针
指向已释放内存的指针。
int* p = new int(42);
delete p; // 释放内存
*p = 100; // 悬挂指针,崩溃!
解决:
delete p;
p = nullptr; // delete后立即置nullptr
学习中的疑问与解答
Q1: new和malloc能混用吗?
A: 不能!new必须配delete,malloc必须配free。混用会导致崩溃或内存泄漏。
Q2: unique_ptr和shared_ptr怎么选?
A:
- 资源只有一个所有者 → unique_ptr
- 资源需要多个对象共享 → shared_ptr
- unique_ptr性能更好,优先考虑
Q3: shared_ptr的线程安全是什么意思?
A:
- ✅ 多个线程同时拷贝shared_ptr是安全的(引用计数是原子的)
- ❌ 多个线程同时修改对象是不安全的(需要加锁)
Q4: weak_ptr什么时候用?
A: 主要用于解决shared_ptr的循环引用问题,比如父子关系、观察者模式。
Q5: 为什么推荐make_shared?
A:
- 性能更好(一次内存分配)
- 异常安全
- 代码更简洁
学习收获
- C++里必须用new/delete,不要用malloc/free
- 智能指针能自动管理内存,避免泄漏
- unique_ptr优先,shared_ptr按需,weak_ptr打破循环
- make_shared比new + shared_ptr更好
- RAII是C++资源管理的核心思想
- 指针用完要置nullptr,避免野指针和悬挂指针
内存管理是C++的核心,智能指针是现代C++的标准做法,必须熟练掌握。
💡 下一步:继续学习课时06(并发编程基础),掌握多线程编程。
📝 本篇整理完成 - 内存管理与智能指针知识体系