【裸机必备知识】嵌入式 MCU 链接脚本里最后那几行 /DISCARD/ 到底是干什么的?为什么几乎所有工程后面都有这玩意儿?
1. 现象
很多 MCU 等裸机工程的链接脚本(.ld 文件)末尾都有这么一段“奇怪”的代码:
/* Remove information from the standard libraries */
/DISCARD/ : {
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
或者,更直观一点,eclipse demo工程内部的ld文件:

乍一看像是“把标准库链接进来了”,其实完全相反——这几行是专门把标准库全部扔掉的!
2. 为什么裸机环境不能留标准库?
| 库名 |
典型函数 |
一旦被链接进来会发生什么? |
| libc.a |
printf、malloc、free、strcpy、exit… |
绝大多数内部调用 syscall(write、sbrk 等),裸机根本没有 → 硬故障 |
| libm.a |
sin、cos、sqrt、pow… |
体积大(几十 KB),而且很多实现又依赖 libc |
| libgcc.a |
__divdi3、__aeabi_ldivmod、浮点辅助 |
部分函数可以安全用,但很多实现也会偷偷调用 libc,体积也很大 |
只要你写了下面任意一种代码,编译器就可能偷偷把它们拖进来:
- uint64_t / int64_t 做除法或取模;
- float / double 类型的运算;
- 没加 -fno-builtin,或者加了但没覆盖全部情况;
- 用了 newlib-nano 但没彻底裁剪;
结果:镜像突然暴涨几 KB 到几百 KB,运行就直接 HardFault。
图1:没有这几行会发生什么?(典型翻车流程)
flowchart TD
A[写了一句 uint64_t a = b / c] --> B[编译器自动拖入 __udivdi3]
B --> C[__udivdi3 来自 libgcc.a]
C --> D[链接时又拖进 libc.a 的 sbrk/write]
D --> E[镜像体积暴涨 50~200KB]
E --> F[运行时调用 write 函数 → HardFault]
style F fill:#f56,color:#fff
3. 这几行到底是怎么工作的?
GNU ld 的规则是:所有没被前面 OUTPUT_SECTION 明确分配的输入段,最后都会被 /DISCARD/ 吞掉。
写成:
libc.a ( * ) → 来自 libc.a 的任何段全部不要
libm.a ( * ) → 同上
libgcc.a ( * ) → 同上
相当于给编译器立了一堵“防火墙”:不管你因为什么原因拖进了标准库的符号,到最后一步统统干掉,强制保持镜像“纯洁”。
图2:链接器处理输入段的顺序
graph TD
subgraph "链接器从上到下处理输入段"
A[普通 SECTION的 .text .data 等段]
--> B[剩余所有未匹配的段]
--> C[DISCARD黑洞 → 直接丢弃]
end
D[libc.a 的 __sbrk.o] --> C
E[libgcc.a 的 __divdi3.o] --> C
F[你的手写代码] --> A
style C fill:#333,color:#fff
4. 常见的几种写法(从温和到极端)
// 推荐写法:明确丢掉四大段,兼容性最好
DISCARD/ : {
libc.a : *(.text* .data* .rodata* .bss*)
libm.a : *(.text* .data* .rodata* .bss*)
libgcc.a : *(.text* .data* .rodata* .bss*)
}
// 更狠的写法(很多开源 Bootloader 或者厂商相关demo就是这么写)
DISCARD/ : {
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
// 有的项目还会顺便把构造函数表也干掉
* ( .ctors .dtors )
*crtbegin.o ( .ctors .dtors )
*crtend.o ( .ctors .dtors )
5. 总结一句话
这几行不是“把标准库链接进来”,而是裸机工程的最后一道保险,防止编译器偷偷拖进来任何依赖操作系统的代码。只要你写的是真正的 bare-metal(无 OS)程序,99% 的链接脚本最后都必须有类似语句,否则迟早翻车。
图3:把它当成裸机项目的“标准结尾咒语”
mindmap
root((裸机链接脚本的discard那一段))
真实作用
防火墙
强制丢弃标准库
防止体积爆炸 & HardFault
不写后果
镜像暴涨
运行必崩
适用场景
Bootloader
RTOS 内核
所有纯裸机工程
结论
99% 项目必须写!
6. 实际建议
- 裸机项目直接把上面那段丢到链接脚本最末尾,可以放心大胆用;
- 如果你确实需要 64 位除法、手写 __aeabi_idivmod 等函数,或者改用 32 位代替;
- 想保留极小的 libc(比如只用 memcpy),可以改用 newlib-nano + --specs=nano.specs,再配合 -nostdlib 手动链接你需要的部分,而不是全扔;