第一章:内存泄漏基础

大家好,我是老张。做机顶盒底层开发十几年了,今天咱们聊聊内存泄漏这个话题。说实话,这玩意儿是嵌入式开发里最让人头疼的问题之一。你想想看,一个机顶盒可能连续运行几个月甚至几年,内存泄漏就像慢性病,慢慢蚕食系统资源,最后让设备彻底罢工。

1.1 什么是内存泄漏

内存泄漏,说白了就是程序向系统申请了一块内存,用完之后忘了还回去。在C/C++里,我们用malloc、new申请的内存,必须用free、delete释放。如果只申请不释放,这块内存就永远被占着,系统再也拿不回来。

核心定义:内存泄漏是指动态分配的内存,在失去所有引用后,没有被正确释放,导致这部分内存无法被回收再利用。

举个简单的例子:

void leak_function() {
    char *buffer = (char *)malloc(1024);
    // 使用buffer...
    // 忘记调用free(buffer);
    return;  // buffer指针丢失,1024字节泄漏
}

这段代码每次调用都会泄漏1024字节。我在项目中遇到过类似的情况,一个后台服务跑了三天,内存从30MB涨到了200MB,最后系统直接OOM(Out Of Memory)了。

1.2 内存泄漏对机顶盒的影响

机顶盒和PC不一样。PC内存不够可以加条子,机顶盒的内存是焊死在板子上的,通常就128MB、256MB。内存泄漏带来的影响非常直接:

影响维度 具体表现 严重程度
系统稳定性 频繁崩溃、重启、死机 致命
用户体验 换台变慢、菜单卡顿、视频花屏 严重
功能异常 EPG加载失败、字幕显示不全 中等
资源竞争 其他进程申请不到内存 严重

我记得有一次,某运营商的机顶盒大规模出现"开机后看两小时就死机"的问题。排查下来,是EPG模块每次刷新节目列表时,都会泄漏几十KB的XML解析内存。累积到一定程度,系统就扛不住了。

特别注意:机顶盒的内存泄漏往往不是立即爆发的。它像温水煮青蛙,可能运行几小时甚至几天后才出问题。这种间歇性故障最难定位。

1.3 常见的内存泄漏场景

做机顶盒开发这些年,我总结了几类最容易出现内存泄漏的场景:

1.3.1 字符串操作

C语言里字符串处理是重灾区。strdup、asprintf这些函数会内部malloc,很多人用完就忘。

// 错误示例
char *get_channel_name(int id) {
    char *name = strdup(channel_list[id].name);
    return name;  // 调用者必须free,但经常被忽略
}

// 正确做法
void get_channel_name(int id, char *buf, int buf_size) {
    strncpy(buf, channel_list[id].name, buf_size - 1);
    buf[buf_size - 1] = '\0';
}

1.3.2 链表和队列操作

机顶盒里到处都是链表——频道列表、节目信息、解码队列。增删节点时,只删节点不删数据,或者忘记释放节点本身,都是常见问题。

我的习惯:每次写链表操作,我都会在删除函数里加个assert,检查节点释放后的内存是否真的归还了。虽然不能完全避免泄漏,但至少能抓住大部分低级错误。

1.3.3 回调函数和异步操作

这个坑我踩过好几次。比如注册了一个定时器回调,回调里分配了内存,但定时器被取消时,回调里的内存没人管了。

// 典型泄漏场景
void timer_callback(void *arg) {
    char *data = (char *)malloc(256);
    // 处理数据...
    // 忘记free(data)
}

void start_timer() {
    timer_register(1000, timer_callback, NULL);
}

void stop_timer() {
    timer_unregister();  // 回调被取消,但已分配的内存无法释放
}

1.3.4 第三方库和驱动

机顶盒离不开各种第三方库——解码库、图形库、网络库。这些库内部的内存管理,有时候并不那么靠谱。我曾经遇到过,某个版本的硬件解码库,每次切换分辨率都会泄漏一帧的显存。

1.3.5 异常路径处理

这是最隐蔽的一类。正常流程下内存释放没问题,但一旦遇到错误、超时、中断,释放代码就被跳过了。

void process_data() {
    char *buf = (char *)malloc(4096);
    if (!buf) return;
    
    int ret = read_data(buf);
    if (ret < 0) {
        // 错误处理,但忘记free(buf)
        return;
    }
    
    // 正常处理...
    free(buf);
}

避坑指南:我曾经因为一个超时处理没释放内存,导致机顶盒在信号不好的地区频繁死机。排查了整整三天才找到原因。从那以后,我写代码都会先画好资源申请和释放的流程图,确保每个出口都有对应的释放操作。

1.4 小结

内存泄漏不是大问题,但累积起来就是大问题。机顶盒这种7x24小时运行的设备,哪怕每天只泄漏1KB,一年下来也是365KB。再加上多模块、多线程的叠加效应,后果不堪设想。

嗯,这一章咱们先把基础概念理清楚。下一章我会讲讲,怎么用工具和方法来定位内存泄漏。到时候我会分享一些实战中总结的"土办法",虽然不高端,但真的很管用。