1、内存泄漏基础:什么是内存泄漏、内存泄漏的危害、C++中内存管理概述
各位同学,咱们今天聊点实在的。内存泄漏,这个词你肯定听过,但真正把它当回事儿的,说实话不多。我刚开始写C++那会儿,也觉得不就是new了没delete嘛,多大点事?直到有一次,我负责的一个后台服务跑了三天,内存从200MB飙到了2GB,然后直接挂了。嗯,从那以后,我再也不敢小看内存泄漏了。
什么是内存泄漏?
说白了,内存泄漏就是:你向系统申请了一块内存,用完之后忘了还回去。系统以为你还在用,别人也用不了,这块内存就这么白白浪费了。
在C++里,最常见的情况就是:
void leakFunction() {
int* p = new int(42);
// 用完了,但忘了 delete p;
// 函数结束,指针p没了,但那块内存还在
}
你看,函数执行完,局部变量p被销毁了,但它指向的那块内存呢?没人管了。系统再也找不到它,也没法回收它。这就是泄漏。
核心定义:内存泄漏是指程序中已动态分配的堆内存,由于某种原因程序未释放或无法释放,造成系统内存的浪费。
内存泄漏的危害
你可能觉得,泄漏一点点内存没什么。我告诉你,这想法很危险。我在项目中见过太多血淋淋的例子了。
危害主要有这么几点:
- 程序越来越慢 —— 内存不够了,系统开始用虚拟内存,频繁的页面交换,性能直线下降。你想想看,本来在内存里操作是纳秒级的,一换到磁盘就变成毫秒级了,差了百万倍。
- 程序崩溃 —— 内存泄漏到一定程度,系统会直接杀掉你的进程。我有个同事,写了个图像处理程序,处理小图片没问题,一处理大图就崩。查了两天,发现是循环里每次new了没delete。
- 系统不稳定 —— 如果是系统服务或者驱动泄漏内存,那整个系统都可能受影响。我曾经遇到过,一个后台服务泄漏内存,导致同一台机器上的其他应用也跟着遭殃。
- 难以排查 —— 内存泄漏不像数组越界那样立刻崩溃,它往往是慢慢积累的。今天跑没事,明天跑也没事,跑了一个星期突然出事。这种问题最难定位。
注意:内存泄漏在嵌入式系统、服务器程序、长时间运行的应用中尤其致命。这些场景下,程序可能连续运行几个月甚至几年,一点点泄漏都会被放大。
C++中内存管理概述
聊到内存泄漏,就不得不说说C++的内存管理。C++给了你极大的自由,但也给了你极大的责任。我个人觉得,这是C++最让人又爱又恨的地方。
C++的内存区域大致分这么几块:
| 内存区域 | 存储内容 | 管理方式 |
|---|---|---|
| 栈(Stack) | 局部变量、函数参数 | 自动管理,函数结束自动释放 |
| 堆(Heap) | 动态分配的对象(new/malloc) | 手动管理,需要delete/free |
| 全局/静态区 | 全局变量、static变量 | 程序启动分配,结束释放 |
| 常量区 | 字符串常量等 | 只读,程序结束释放 |
你看,栈上的变量是自动管理的,函数一结束就没了。但堆上的内存,你得自己管。这就是内存泄漏的根源所在。
在C++里,内存管理主要有三种方式:
- 手动管理 —— 用
new/delete,malloc/free。这是最原始的方式,也是最容易出错的。我建议新手尽量少用。 - RAII(资源获取即初始化) —— 把资源绑定到对象的生命周期上。对象构造时获取资源,析构时释放资源。这是C++的核心思想之一。
- 智能指针 —— C++11引入的
shared_ptr、unique_ptr、weak_ptr。说白了就是RAII的封装,帮你自动管理内存。
我的建议:在现代C++中,尽量使用智能指针。我现在的项目里,几乎看不到裸的new/delete了。除非是性能极其敏感的场景,否则别自己手动管理内存。
嗯,这里有个关键点要记住:智能指针不是万能的。循环引用会导致shared_ptr泄漏,这个我们后面会详细讲。
本章知识体系
为了让你更直观地理解本章的内容,我画了一张图:
这张图把本章的核心内容串起来了。从内存泄漏的定义出发,到它的危害,再到C++的内存管理方式。说白了,你只有理解了内存是怎么分配的,才能知道泄漏是怎么发生的。
好了,这一章就到这里。记住一句话:内存泄漏不是会不会发生的问题,而是什么时候发生的问题。下一章我们聊聊如何检测内存泄漏,我会分享一些我实际用过的工具和方法。
课后思考:你可以在自己的项目里试试,用Valgrind或者AddressSanitizer跑一下,看看有没有内存泄漏。我保证,结果会让你大吃一惊。