返回列表 发新帖我要提问本帖赏金: 30.00元(功能说明)

[开发工具] 【裸机必备知识】嵌入式 MCU 链接脚本里最后那几行 /DISCARD/ 到底是干什么的?

[复制链接]
474|5
DKENNY 发表于 2025-12-9 14:46 | 显示全部楼层 |阅读模式

【裸机必备知识】嵌入式 MCU 链接脚本里最后那几行 /DISCARD/ 到底是干什么的?为什么几乎所有工程后面都有这玩意儿?

1. 现象

很多 MCU 等裸机工程的链接脚本(.ld 文件)末尾都有这么一段“奇怪”的代码:

/* Remove information from the standard libraries */
/DISCARD/ : {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
}

或者,更直观一点,eclipse demo工程内部的ld文件:

图示.png

乍一看像是“把标准库链接进来了”,其实完全相反——这几行是专门把标准库全部扔掉的

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 手动链接你需要的部分,而不是全扔;

打赏榜单

21小跑堂 打赏了 30.00 元 2025-12-31
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论

详细解读嵌入式MCU裸机开发时链接脚本最后的几行代码的实际意义,一个很容易忽略的底层细节。  发表于 2025-12-31 10:57
 楼主| DKENNY 发表于 2025-12-9 14:46 | 显示全部楼层
#申请原创# #技术资源#@21小跑堂
Gfan 发表于 2025-12-9 16:14 | 显示全部楼层
好顶赞,非常切实的科普向知识,对初学者来说是必不可少的
SpiritSong 发表于 2025-12-9 23:28 | 显示全部楼层
C语言的知识还是要再加强。这些知识都知道的有限
cooldog123pp 发表于 2026-1-26 13:40 | 显示全部楼层
我去楼主挖掘的这么细节,我从来没有关心过这个文件的代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则

83

主题

158

帖子

20

粉丝
快速回复 在线客服 返回列表 返回顶部
0