1. 代码压缩入门:为什么需要代码压缩?
做嵌入式开发的朋友,十有八九都遇到过这种情况:辛辛苦苦写完功能,编译一过,发现 Flash 快满了。或者跑起来总觉得卡顿,查了半天发现是代码体积太大,把缓存挤爆了。
我刚开始做单片机项目时也犯过这毛病。那时候觉得反正 Flash 有 64KB,够用。结果加了个协议栈,再加个 GUI 库,直接超了 20%。最后只能硬着头皮砍功能、手写汇编——那叫一个痛苦。
说白了,代码压缩就是让你的程序在更小的存储空间里跑得更快。这不是锦上添花,而是嵌入式开发的必修课。
核心观点: 代码压缩不是“优化完再考虑”的事,而是从设计第一天就该刻在骨子里的意识。
1.1 代码膨胀的代价
代码膨胀到底有多贵?我列几个真实账目给你看。
| 资源类型 | 膨胀后果 | 典型代价 |
|---|---|---|
| Flash 容量 | 超出芯片规格,被迫换更大容量型号 | 每增加 64KB,BOM 成本上升 $0.3~$1.5 |
| RAM 占用 | 全局变量/栈空间被挤占,动态分配频繁 | 堆碎片导致随机崩溃,调试成本翻倍 |
| 指令缓存 | 代码段过大,I-Cache 命中率下降 | 执行效率降低 10%~40% |
| 编译/链接时间 | 冗余代码导致链接器处理更多符号 | 每次修改等待时间增加 30 秒~2 分钟 |
| 维护成本 | 重复代码、死代码、魔数散落各处 | 改一处漏三处,线上 Bug 率上升 |
你看,代码膨胀不只是“多用几 KB”的问题。它会从成本、性能、可靠性三个维度同时拖垮你的项目。
我曾经踩过的坑: 有个量产产品用了 STM32F103C8T6(64KB Flash),固件编译后 62KB。当时觉得还剩 2KB 没问题。结果客户要求加一个 OTA 升级功能,Flash 直接爆了。最后只能换 F103RBT6(128KB),单价贵了 $0.8,一年 10 万片就是 8 万美金——就因为当初没做代码压缩。
1.2 代码膨胀的根源
为什么会膨胀?我总结了几条最常见的“凶手”。
- 滥用库函数: 调用一个
printf,可能拉进来 5KB 的格式化代码。你只是打印个整数,却背上了整个 stdio 的重量。 - 模板/泛型展开: C++ 模板每个类型实例化都会生成一份完整代码。你写了一个
List<int>和一个List<float>,编译器就给你生成两份几乎一样的代码。 - 内联过度: 把大函数加
inline,每次调用都复制一份。代码体积直接翻倍。 - 死代码残留: 注释掉的函数、未调用的中断处理、调试用的打印语句——它们都躺在 Flash 里吃空间。
- 对齐填充: 结构体成员没按自然边界对齐,编译器插入大量 padding 字节。一个 10 字节的结构体,可能实际占用 16 甚至 20 字节。
一个小实验: 你可以在当前项目里搜一下
printf、sprintf、std::vector 这些关键词。然后注释掉它们,重新编译看看 Flash 减少了多少。我试过,最多一次减了 12KB。
1.3 代码压缩的核心收益
压缩代码不是让你写晦涩难懂的汇编。恰恰相反,好的压缩能让代码更清晰、更高效。
- 降低成本: 用更便宜的芯片完成同样功能。每片省 $0.5,10 万片就是 5 万美金。
- 提升性能: 代码越小,I-Cache 命中率越高。同样的 CPU 频率,实际吞吐量可能提升 20%。
- 减少 Bug: 冗余代码是 Bug 的温床。删掉死代码、合并重复逻辑,出问题的概率自然下降。
- 加快启动: 代码段越小,从 Flash 加载到 RAM 的时间越短。有些设备要求上电 100ms 内响应,压缩代码就是关键。
1.4 代码压缩的层次
代码压缩不是一招鲜。我习惯把它分成四个层次,从浅到深。
我个人习惯是:先做第1层和第2层。这两个层次改动小、风险低,通常能砍掉 20%~40% 的代码体积。如果还不够,再考虑第3层和第4层。
我的经验: 80% 的项目在第1层和第2层就能解决问题。只有那些对成本极度敏感(比如消费电子、IoT 模组)或者对实时性要求极高的场景,才需要深入到第3层和第4层。
1.5 一个简单的对比实验
光说不练假把式。我们看一段真实代码。
// 优化前:滥用 printf
void debug_print(int val) {
printf("Current value: %d\n", val);
}
// 优化后:用自定义轻量打印
void debug_print(int val) {
uart_send_str("CV:");
uart_send_hex(val);
uart_send_char('\n');
}
第一段代码用了 printf,链接器会把整个格式化输出模块拉进来。第二段只用了三个自定义函数,体积小得多。
我在一个 STM32G030 项目上实测过:printf 版本占 18.2KB,轻量版本只占 6.8KB。就改了一行调用,省了 11.4KB——相当于芯片容量从 32KB 降到了 16KB。
注意: 不要盲目禁用所有库函数。有些库函数(比如
memcpy、memset)是经过高度优化的,比自己写的快得多。压缩代码的前提是“不影响功能正确性和性能指标”。
1.6 什么时候该做代码压缩?
不是所有项目都需要极致压缩。我一般按这个标准判断:
- Flash 占用率 > 80%: 必须做。否则后续加功能、修 Bug 都会很被动。
- Flash 占用率 60%~80%: 建议做。提前压缩,给未来留余量。
- Flash 占用率 < 60%: 可以不做。但要注意,如果项目生命周期长(比如 5 年以上),还是建议养成压缩习惯。