C++ 内存管理与内存泄漏
C++ 提供了强大的内存管理能力,但也要求开发者对内存生命周期有清晰的认识。不当的内存管理会导致内存泄漏、悬空指针、重复释放等严重问题。
C++ 内存区域
1. 栈(Stack)
- 存储局部变量、函数参数、返回地址
- 自动分配和释放
- 大小有限(通常几 MB)
- 分配速度快
2. 堆(Heap)
- 动态分配的内存
- 手动管理(new/delete)或智能指针管理
- 大小受限于系统可用内存
- 分配速度较慢
3. 全局/静态区
- 存储全局变量、静态变量
- 程序启动时分配,结束时释放
- 生命周期贯穿整个程序
4. 常量区
- 存储字符串常量、const 变量
- 只读,不可修改
5. 代码区
- 存储程序二进制代码
- 只读
内存泄漏的原因
1. 忘记释放内存
cppvoid leakExample() { int* ptr = new int(42); // 忘记 delete ptr; }
2. 异常导致跳过释放
cppvoid leakWithException() { int* ptr = new int(42); someFunctionThatThrows(); // 如果抛出异常,ptr 不会被释放 delete ptr; }
3. 循环引用
cppclass A { public: std::shared_ptr<B> b; }; class B { public: std::shared_ptr<A> a; // 循环引用导致内存泄漏 }; auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b = b; b->a = a;
4. 不当的指针赋值
cppvoid lostPointer() { int* ptr = new int(42); ptr = new int(100); // 第一个分配的内存泄漏 delete ptr; }
检测内存泄漏
1. Valgrind(Linux)
bashvalgrind --leak-check=full --show-leak-kinds=all ./your_program
2. AddressSanitizer(ASan)
bashg++ -fsanitize=address -g your_program.cpp -o your_program ./your_program
3. Visual Studio 调试器
- 使用 CRT 调试堆
- 在代码开头添加:
cpp#define _CRTDBG_MAP_ALLOC #include <crtdbg.h> _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
4. 自定义内存跟踪
cppclass MemoryTracker { private: static std::unordered_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) { if (allocations.erase(ptr) == 0) { std::cerr << "Double free or invalid pointer: " << ptr << std::endl; } free(ptr); } static void reportLeaks() { if (!allocations.empty()) { std::cerr << "Memory leaks detected:" << std::endl; for (const auto& [ptr, size] : allocations) { std::cerr << " Leaked " << size << " bytes at " << ptr << std::endl; } } } };
防止内存泄漏的方法
1. 使用智能指针
cpp// 不推荐 void badExample() { Resource* res = new Resource(); // 容易忘记 delete } // 推荐 void goodExample() { auto res = std::make_unique<Resource>(); // 自动释放 }
2. 遵循 RAII 原则
cppclass FileHandler { private: FILE* file; public: FileHandler(const char* filename) { file = fopen(filename, "r"); if (!file) { throw std::runtime_error("Failed to open file"); } } ~FileHandler() { if (file) { fclose(file); } } // 禁止拷贝 FileHandler(const FileHandler&) = delete; FileHandler& operator=(const FileHandler&) = delete; };
3. 使用标准容器
cpp// 不推荐 void badContainer() { int* arr = new int[100]; // 处理数组 delete[] arr; } // 推荐 void goodContainer() { std::vector<int> arr(100); // 自动管理内存 }
4. 正确处理异常
cpp// 不推荐 void badException() { int* ptr = new int(42); riskyOperation(); // 可能抛出异常 delete ptr; } // 推荐 void goodException() { auto ptr = std::make_unique<int>(42); riskyOperation(); // 即使抛出异常,ptr 也会被正确释放 }
内存对齐
为什么需要内存对齐:
- CPU 访问对齐的内存更快
- 某些架构要求特定类型必须对齐
- 避免性能下降或程序崩溃
对齐方式:
cpp// 使用 alignas 指定对齐 struct alignas(16) AlignedStruct { int a; double b; }; // 使用 alignof 查询对齐要求 std::cout << "Alignment: " << alignof(AlignedStruct) << std::endl; // 使用 aligned_alloc 分配对齐内存 void* ptr = aligned_alloc(16, 1024);
内存池
内存池的优点:
- 减少内存碎片
- 提高分配/释放速度
- 减少系统调用次数
简单实现:
cpptemplate <typename T, size_t BlockSize = 1024> class MemoryPool { private: struct Block { T data; Block* next; }; Block* freeList; std::vector<std::unique_ptr<Block[]>> blocks; public: MemoryPool() : freeList(nullptr) { allocateBlock(); } ~MemoryPool() = default; T* allocate() { if (!freeList) { allocateBlock(); } Block* block = freeList; freeList = freeList->next; return &block->data; } void deallocate(T* ptr) { Block* block = reinterpret_cast<Block*>(ptr); block->next = freeList; freeList = block; } private: void allocateBlock() { auto newBlock = std::make_unique<Block[]>(BlockSize); for (size_t i = 0; i < BlockSize - 1; ++i) { newBlock[i].next = &newBlock[i + 1]; } newBlock[BlockSize - 1].next = nullptr; freeList = &newBlock[0]; blocks.push_back(std::move(newBlock)); } };
最佳实践
1. 优先使用栈内存
cpp// 优先 void stackPreferred() { int value = 42; process(value); } // 仅在必要时使用堆 void heapWhenNeeded() { auto value = std::make_unique<int>(42); process(*value); }
2. 使用标准库容器
cppstd::vector<int> vec; // 自动管理内存 std::string str; // 自动管理内存 std::map<int, int> map; // 自动管理内存
3. 避免裸指针
cpp// 不推荐 int* ptr = new int(42); // ... 使用 ptr delete ptr; // 推荐 auto ptr = std::make_unique<int>(42); // ... 使用 ptr // 自动释放
4. 使用 const 正确性
cppvoid process(const std::vector<int>& data); // 避免拷贝 void modify(std::vector<int>& data); // 明确表示会修改
5. 定期进行内存分析
bash# 使用 Valgrind 检测内存泄漏 valgrind --leak-check=full --show-leak-kinds=all ./your_program # 使用 AddressSanitizer g++ -fsanitize=address -g your_program.cpp -o your_program ./your_program
常见错误
1. 重复释放
cppint* ptr = new int(42); delete ptr; delete ptr; // 未定义行为
2. 释放未分配的内存
cppint* ptr; delete ptr; // 未定义行为
3. 数组 new/delete 不匹配
cppint* arr = new int[10]; delete arr; // 错误,应该使用 delete[] arr
4. 栈内存使用 delete
cppint value = 42; int* ptr = &value; delete ptr; // 错误,不能释放栈内存
5. 悬空指针
cppint* ptr = new int(42); delete ptr; *ptr = 100; // 未定义行为,悬空指针