·34 分钟
C++ 内存管理深入解析:从堆栈到智能指针
C++内存管理智能指针RAIIC/C++
内存区域概览
C++ 程序的内存分为几个区域,每个区域有不同的特性和用途:
code
┌─────────────────────┐ 高地址
│ 栈 (Stack) │ ← 局部变量、函数参数
├─────────────────────┤
│ ↓ │
│ │
│ ↑ │
├─────────────────────┤
│ 堆 (Heap) │ ← 动态分配(new/malloc)
├─────────────────────┤
│ 全局/静态存储区 │ ← 全局变量、静态变量
├─────────────────────┤
│ 常量存储区 │ ← 字符串常量等
├─────────────────────┤
│ 代码区 (Code) │ ← 程序代码
└─────────────────────┘ 低地址
1. 栈(Stack)
特点:
- 由编译器自动管理
- 空间有限(通常 1-8MB)
- 分配/释放速度极快(移动栈指针)
- 后进先出(LIFO)
cpp
void function() {
int a = 10; // 栈上分配
int arr[100]; // 栈上分配
std::string str = "hello"; // str 对象在栈上,数据在堆上
// 函数结束时自动释放
}
栈溢出:
cpp
void stackOverflow() {
int arr[10000000]; // 可能导致栈溢出
// 解决:使用堆分配
int* arr2 = new int[10000000];
delete[] arr2;
}
2. 堆(Heap)
特点:
- 由程序员手动管理
- 空间大(受系统内存限制)
- 分配/释放速度较慢
- 可能产生内存碎片
cpp
void heapExample() {
// C 风格
int* p1 = (int*)malloc(sizeof(int));
free(p1);
// C++ 风格
int* p2 = new int(10);
delete p2;
// 数组
int* arr = new int[100];
delete[] arr;
}
3. 全局/静态存储区
cpp
int globalVar = 10; // 全局变量
static int staticVar = 20; // 静态变量
void function() {
static int localStatic = 30; // 静态局部变量
// 生命周期持续到程序结束
}
内存分配详解
1. new/delete 的实现
cpp
// new 的执行过程
int* p = new int(10);
// 等价于:
void* memory = operator new(sizeof(int)); // 分配内存
int* p = static_cast<int*>(memory);
*p = 10; // 初始化
// delete 的执行过程
delete p;
// 等价于:
p->~int(); // 析构(对于对象)
operator delete(p); // 释放内存
2. 内存池(Memory Pool)
问题:频繁 new/delete 导致内存碎片
解决方案:内存池
cpp
#include <vector>
#include <cstddef>
class MemoryPool {
private:
struct Block {
Block* next;
};
Block* freeList;
std::vector<char*> chunks;
size_t blockSize;
size_t chunkSize;
public:
MemoryPool(size_t blockSize, size_t chunkSize = 1024)
: blockSize(blockSize), chunkSize(chunkSize), freeList(nullptr) {
allocateChunk();
}
~MemoryPool() {
for (char* chunk : chunks) {
delete[] chunk;
}
}
void* allocate() {
if (!freeList) {
allocateChunk();
}
Block* block = freeList;
freeList = freeList->next;
return block;
}
void deallocate(void* ptr) {
Block* block = static_cast<Block*>(ptr);
block->next = freeList;
freeList = block;
}
private:
void allocateChunk() {
char* chunk = new char[blockSize * chunkSize];
chunks.push_back(chunk);
for (size_t i = 0; i < chunkSize; i++) {
Block* block = reinterpret_cast<Block*>(chunk + i * blockSize);
block->next = freeList;
freeList = block;
}
}
};
// 使用示例
struct MyStruct {
int data[10];
};
MemoryPool pool(sizeof(MyStruct));
MyStruct* obj = static_cast<MyStruct*>(pool.allocate());
// 使用 obj...
pool.deallocate(obj);
3. 定位 new(Placement New)
cpp
#include <new>
// 在已分配的内存上构造对象
char buffer[sizeof(MyStruct)];
MyStruct* obj = new (buffer) MyStruct(); // 定位 new
// 必须手动调用析构函数
obj->~MyStruct();
// 应用场景:
// 1. 内存池
// 2. 自定义内存管理
// 3. 避免内存碎片
智能指针
1. std::unique_ptr
特点:独占所有权,不能拷贝,只能移动
cpp
#include <memory>
// 创建
std::unique_ptr<int> p1(new int(10));
auto p2 = std::make_unique<int>(20); // C++14,推荐
// 不能拷贝
// std::unique_ptr<int> p3 = p1; // 错误
// 可以移动
std::unique_ptr<int> p3 = std::move(p1);
// p1 现在为 nullptr
// 自定义删除器
auto deleter = [](FILE* fp) { fclose(fp); };
std::unique_ptr<FILE, decltype(deleter)> fp(fopen("test.txt", "r"), deleter);
// 数组版本
std::unique_ptr<int[]> arr(new int[100]);
arr[0] = 10;
// 使用场景
// 1. 动态分配的对象
// 2. 资源管理(文件、网络连接等)
// 3. 工厂函数返回值
2. std::shared_ptr
特点:共享所有权,引用计数
cpp
#include <memory>
// 创建
std::shared_ptr<int> p1(new int(10));
auto p2 = std::make_shared<int>(20); // 推荐,更高效
// 拷贝和赋值
std::shared_ptr<int> p3 = p1; // 引用计数 +1
p3 = p2; // p1 引用计数 -1,p2 引用计数 +1
// 引用计数
std::cout << p1.use_count() << std::endl; // 输出引用计数
// 自定义删除器
std::shared_ptr<int> p4(new int(10), [](int* p) {
std::cout << "Custom deleter\n";
delete p;
});
// 循环引用问题
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "Node destroyed\n"; }
};
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1; // 循环引用,内存泄漏!
3. std::weak_ptr
特点:弱引用,不增加引用计数,解决循环引用
cpp
#include <memory>
// 解决循环引用
struct Node {
std::weak_ptr<Node> next; // 使用 weak_ptr
~Node() { std::cout << "Node destroyed\n"; }
};
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->next = node1; // 不会导致内存泄漏
// 使用 weak_ptr
std::weak_ptr<int> wp;
{
auto sp = std::make_shared<int>(10);
wp = sp;
// 检查是否有效
if (auto locked = wp.lock()) {
std::cout << *locked << std::endl; // 安全访问
}
}
// sp 销毁后,wp.lock() 返回 nullptr
// 应用场景:
// 1. 打破循环引用
// 2. 缓存系统
// 3. 观察者模式
4. 智能指针最佳实践
cpp
// 1. 优先使用 make_shared/make_unique
auto p1 = std::make_shared<int>(10); // 推荐
std::shared_ptr<int> p2(new int(10)); // 不推荐
// 2. 避免混用原始指针和智能指针
void bad(std::shared_ptr<int> p1, std::shared_ptr<int> p2) {}
int* raw = new int(10);
bad(std::shared_ptr<int>(raw), std::shared_ptr<int>(raw)); // 错误!
// 3. 使用 unique_ptr 作为默认选择
std::unique_ptr<int> getDefault() {
return std::make_unique<int>(10);
}
// 4. 只在需要共享所有权时使用 shared_ptr
class Resource {
std::shared_ptr<Data> data; // 多个 Resource 共享数据
};
// 5. 避免返回 shared_ptr 的原始指针
class Bad {
std::shared_ptr<int> data;
public:
int* get() { return data.get(); } // 危险!
};
RAII 模式
1. 什么是 RAII
RAII(Resource Acquisition Is Initialization):
- 资源获取即初始化
- 在构造函数中获取资源
- 在析构函数中释放资源
- 利用栈对象的自动销毁特性
cpp
// RAII 封装文件操作
class FileHandle {
private:
FILE* fp;
public:
FileHandle(const char* filename, const char* mode)
: fp(fopen(filename, mode)) {
if (!fp) {
throw std::runtime_error("Failed to open file");
}
}
~FileHandle() {
if (fp) {
fclose(fp);
}
}
// 禁止拷贝
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 允许移动
FileHandle(FileHandle&& other) noexcept : fp(other.fp) {
other.fp = nullptr;
}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (fp) fclose(fp);
fp = other.fp;
other.fp = nullptr;
}
return *this;
}
// 提供访问接口
FILE* get() const { return fp; }
operator bool() const { return fp != nullptr; }
};
// 使用
void writeFile() {
FileHandle file("test.txt", "w");
if (file) {
fprintf(file.get(), "Hello, RAII!");
}
// 离开作用域自动关闭文件
}
2. RAII 应用场景
cpp
// 1. 互斥锁
class LockGuard {
private:
std::mutex& mtx;
public:
LockGuard(std::mutex& m) : mtx(m) {
mtx.lock();
}
~LockGuard() {
mtx.unlock();
}
LockGuard(const LockGuard&) = delete;
LockGuard& operator=(const LockGuard&) = delete;
};
// 2. 内存管理
class Buffer {
private:
char* data;
size_t size;
public:
Buffer(size_t sz) : size(sz), data(new char[sz]) {}
~Buffer() { delete[] data; }
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
char* get() { return data; }
size_t getSize() const { return size; }
};
// 3. 数据库连接
class DatabaseConnection {
private:
Connection* conn;
public:
DatabaseConnection(const std::string& url)
: conn(connect(url)) {
if (!conn) {
throw std::runtime_error("Connection failed");
}
}
~DatabaseConnection() {
if (conn) {
disconnect(conn);
}
}
Connection* get() { return conn; }
};
内存泄漏检测
1. 常见内存泄漏场景
cpp
// 1. 忘记释放
void leak1() {
int* p = new int(10);
// 忘记 delete p
}
// 2. 异常导致泄漏
void leak2() {
int* p = new int(10);
throw std::runtime_error("error"); // p 泄漏!
delete p;
}
// 3. 循环引用
struct Node {
std::shared_ptr<Node> next;
};
void leak3() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2;
n2->next = n1; // 循环引用
}
// 4. 基类析构函数非虚
class Base {
public:
~Base() {} // 非虚析构函数
};
class Derived : public Base {
int* data;
public:
Derived() : data(new int(10)) {}
~Derived() { delete data; }
};
void leak4() {
Base* p = new Derived();
delete p; // 不会调用 Derived 的析构函数!
}
2. 检测工具
bash
# 1. Valgrind(Linux)
valgrind --leak-check=full ./program
# 2. AddressSanitizer(GCC/Clang)
g++ -fsanitize=address -g program.cpp
./a.out
# 3. Visual Studio 内存检测器
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
int main() {
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
// 程序结束时自动检测泄漏
}
# 4. 自定义内存跟踪
class MemoryTracker {
static std::map<void*, size_t> allocations;
public:
static void* allocate(size_t size) {
void* ptr = malloc(size);
allocations[ptr] = size;
return ptr;
}
static void deallocate(void* ptr) {
allocations.erase(ptr);
free(ptr);
}
static void report() {
std::cout << "Memory leaks: " << allocations.size() << std::endl;
for (auto& [ptr, size] : allocations) {
std::cout << " " << ptr << ": " << size << " bytes\n";
}
}
};
最佳实践总结
内存管理原则
- 优先使用栈对象:自动管理生命周期
- 使用 RAII:封装资源管理
- 使用智能指针:避免手动 new/delete
- 避免裸指针:除非必要
- 明确所有权:谁负责释放
智能指针选择
| 场景 | 选择 |
|---|---|
| 独占所有权 | unique_ptr |
| 共享所有权 | shared_ptr |
| 打破循环引用 | weak_ptr |
| 需要自定义删除器 | unique_ptr 或 shared_ptr |
性能考虑
cpp
// 1. make_shared 更高效
auto p1 = std::make_shared<int>(10); // 一次内存分配
std::shared_ptr<int> p2(new int(10)); // 两次内存分配
// 2. 避免不必要的 shared_ptr
void process(std::shared_ptr<int> p) { // 拷贝,增加引用计数
// 如果不需要共享所有权,用 unique_ptr 或引用
}
void process(int& ref) { // 更高效
// ...
}
// 3. 使用移动语义
std::unique_ptr<int> create() {
auto p = std::make_unique<int>(10);
return p; // 移动,不是拷贝
}
总结
核心概念
- 内存区域:栈、堆、全局/静态、常量、代码
- 分配方式:new/delete、malloc/free、智能指针
- 管理策略:RAII、智能指针、内存池
- 检测工具:Valgrind、AddressSanitizer、自定义跟踪
常见错误
- 忘记释放内存
- 重复释放
- 使用已释放的内存
- 循环引用
- 基类析构函数非虚
学习资源
延伸阅读: