1. 内存泄漏基础概念

各位同学,咱们今天聊一个嵌入式开发里特别容易踩坑的话题——内存泄漏。说实话,我刚开始做单片机项目那会儿,觉得内存嘛,总共就那么几十K,分配了用完了就完事了。直到有一次,一个跑了一个多月的设备突然死机,查了三天才发现是内存泄漏。从那以后,我再也不敢小看这个问题了。

1.1 什么是内存泄漏

内存泄漏,说白了就是:你向系统申请了一块内存,用完之后忘了还回去。这块内存就一直被占用着,谁也动不了。

在嵌入式系统里,内存资源本来就紧张。你想想看,一个STM32F103可能只有20K的RAM。如果每次泄漏几十个字节,跑上几个小时、几天,内存就被慢慢吃光了。

核心定义:内存泄漏 = 动态分配的内存 + 丢失了指向它的指针 + 无法释放

我见过不少新手写代码,malloc之后直接赋值给局部指针,函数一返回,指针变量没了,那块内存就成了孤儿。谁也找不到它,谁也释放不了它。

1.2 内存泄漏对嵌入式系统的危害

在PC上,内存泄漏可能只是让电脑变慢。但在单片机上,后果严重得多。我列几个真实案例:

危害类型 具体表现 我遇到过的案例
系统崩溃 malloc返回NULL,程序没做检查,直接写空指针 某工业控制器,运行72小时后死机
功能异常 内存碎片化严重,大块分配失败 通信协议栈突然收不到数据
性能下降 内存分配越来越慢,任务响应延迟 GUI界面卡顿,触摸反应迟钝
无法恢复 看门狗复位后,泄漏代码继续执行 设备反复重启,现场维护成本极高

嗯,这里要注意一点:嵌入式系统很多是7×24小时运行的。一个每小时泄漏10字节的程序,跑一个月就是7.2K。对于只有64K RAM的芯片来说,这已经是很严重的消耗了。

避坑指南:我曾经在一个物联网项目中,就因为一个char*没释放,导致设备在运行第45天时集体离线。那次教训让我养成了一个习惯——每次malloc后面必须紧跟free,就像开门必须关门一样自然。

1.3 常见的内存分配方式

嵌入式开发里,动态内存分配主要有两套体系。咱们一个一个说。

1.3.1 C语言:malloc / free

这是最常用的方式。malloc从堆上申请一块连续内存,返回void*指针。free负责归还。

// 典型用法
uint8_t *buffer = (uint8_t *)malloc(256);
if (buffer == NULL) {
    // 分配失败,必须处理
    Error_Handler();
}
// 使用buffer...
free(buffer);
buffer = NULL;  // 我习惯置空,防止野指针

这里有几个坑,我重点说一下:

  • malloc可能失败:堆空间不足或碎片化时返回NULL。很多新手不检查返回值,直接写内存,后果就是hard fault。
  • free之后指针还在:它变成了野指针。我建议立即赋值为NULL。
  • 重复free:对同一块内存释放两次,会导致堆管理结构损坏。

1.3.2 C++:new / delete

在C++嵌入式项目里,new和delete更常用。它们不仅分配内存,还会调用构造函数和析构函数。

// C++ 方式
class SensorData {
public:
    SensorData() { /* 初始化 */ }
    ~SensorData() { /* 清理 */ }
private:
    uint8_t data[128];
};

SensorData *sensor = new SensorData();
if (sensor == nullptr) {
    // 处理失败
}
// 使用...
delete sensor;
sensor = nullptr;

个人经验:在资源受限的MCU上,我其实不太推荐频繁使用new/delete。因为C++的异常处理和构造析构会增加代码体积和运行时开销。我一般只在RTOS任务间传递大块数据时才会用。

1.3.3 两种方式的对比

对比项 malloc / free new / delete
语言 C C++
是否调用构造/析构 否 是
返回类型 void*,需强制转换 类型安全,无需转换
失败处理 返回NULL 返回nullptr或抛出异常
代码体积 较小 较大(含异常处理)
适用场景 裸机、RTOS C++项目、复杂对象

1.4 内存泄漏的典型场景

我总结了几种最容易出现泄漏的情况,你对照看看自己有没有中过招:

  1. 函数内分配,忘记释放——局部指针,函数返回后内存丢失
  2. 异常分支没处理——正常路径有free,错误路径直接return了
  3. 链表/队列操作失误——删除节点时只删了指针,没释放内存
  4. RTOS消息队列——发送动态内存,接收方忘了释放
  5. 重分配覆盖——ptr = realloc(ptr, new_size); 失败时原指针丢失

核心原则:谁分配,谁释放。如果跨模块传递,一定要明确所有权转移的规则。

1.5 本章知识体系

下面这张图,把内存泄漏的核心概念串起来了。你可以把它当作本章的思维导图:

内存泄漏知识体系 内存泄漏 定义:申请未归还 危害:崩溃/异常/性能下降 分配方式:malloc/free vs new/delete 典型泄漏场景 • 函数内分配未释放 • 异常分支遗漏 • 链表/队列操作失误 避坑方法 • 成对编写malloc/free • 检查返回值 • 释放后置空指针 核心原则:谁分配,谁释放

这张图把内存泄漏的四个维度串起来了:定义、危害、分配方式、典型场景。你写代码的时候,脑子里要有这张图。每次malloc,都问自己一句:这个free写在哪?

我的习惯:写代码时,malloc和free成对写。先写free那行,再回头写中间的逻辑。这样就不会忘了。你试试看,这个方法帮我少踩了无数坑。