单片机项目中--gc-sections不生效的根本原因是编译器默认将函数合并到同一段(如.text),而该选项仅按段粒度裁剪;必须配合-ffunction-sections(编译阶段)使每个函数独立成段,再结合-fdata-sections和--gc-sections才能真正剔除未引用的函数与数据。

为什么 --gc-sections 在单片机项目里经常不生效
根本原因不是链接器没干活,而是编译器默认把函数合并进同一个代码段(比如 .text),而 --gc-sections 只能按「段(section)」粒度裁剪——整段只要有一个符号被引用,整段就全留下。
单片机固件通常用 -ffunction-sections 配合 --gc-sections 才能真正剔除未调用的函数。否则你看到 size 输出没变、nm 里仍有一堆死函数,都是正常现象。
-ffunction-sections 必须加在编译阶段,不是链接阶段
这个选项控制的是编译器(gcc / g++)如何生成目标文件:每个函数单独放进一个命名段,如 .text.foo、.text.bar。链接器后续才能按需保留或丢弃这些细粒度段。
常见错误写法:
arm-none-eabi-gcc main.o -Wl,--gc-sections -o firmware.elf # ❌ 缺少 -ffunction-sections 编译选项
正确做法(CMake 为例):
- 在
target_compile_options()中为所有源文件添加-ffunction-sections - 链接时加
-Wl,--gc-sections(-Wl,表示把后面的参数传给链接器) - 强烈建议同时加
-Wl,--print-gc-sections查看哪些段被删了(调试用,发布时去掉)
别忘了 -fdata-sections 和 --gc-sections 的协同作用
全局变量、const 数据(比如字符串字面量、查找表)同样会常驻固件。如果只开 -ffunction-sections,这些数据仍可能拖住整个 .rodata 段不被回收。
所以实际项目应成对使用:
-
-ffunction-sections→ 每个函数独立段 -
-fdata-sections→ 每个全局/静态变量独立段(包括const) -
-Wl,--gc-sections→ 链接时删除未引用的函数段和数据段
注意:-fdata-sections 对栈上变量、局部 static 变量无效,只影响文件作用域的变量。
检查是否真生效:用 readelf 和 nm 看段级变化
光看 size firmware.elf 不够——它显示的是 ELF 文件大小,不是 Flash 实际占用。重点看 .text 和 .rodata 的实际加载尺寸:
arm-none-eabi-readelf -S firmware.elf | grep '\.text\|\.rodata'
更直接的方式是反查符号:
-
arm-none-eabi-nm -C --undefined-only firmware.elf看有没有意外的未定义引用(可能阻止 GC) -
arm-none-eabi-objdump -t firmware.elf | grep ' F .text\| O .rodata'列出所有函数和只读数据段,确认无用函数是否已消失 - 如果某函数仍存在但你确定没调用,检查是否被宏展开、模板实例化、或中断向量表隐式引用(例如
__isr_vector引用了Default_Handler,而它又可能间接引用其他函数)
中断向量表、启动代码、弱符号定义(__attribute__((weak)))都可能成为 GC 的“锚点”,让本该删除的函数意外存活。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











