当前位置: 首页 > ops >正文

为何重定义库函数会减少flash体积(从prinf讲解)

备注:使用keil的microlib库进行实验,printf的底层实现是通过fputc()来实现

1. 为什么要重定义 fputc

  • 默认实现:在 ARM/Keil Microlib 里,printf 等最终都会调用 fputc。如果用户没提供,库里的 fputc.o 会被用上。

  • 库版 fputc 的问题

    • 它默认走 半主机 (semihosting),通过调试器把输出送到 PC 主机。

    • 脱机运行时可能触发 HardFault(因为 BKPT/调试协议无人响应)。

    • 它依赖 semi.oiusesemip.o 等模块,把一坨用不到的代码拖进来,增大 Flash 占用。

  • 重定义的好处

    • 把输出重定向到 UART/SWO/ITM 等实际外设。

    • 避免半主机依赖 → 节省 Flash。

    • 程序可脱机独立运行。

2. 链接器的行为和原理

  • 规则:“谁需要,谁没定义,就从库里拉进来”。

    • 程序用到 printf → 未定义符号 fputc

    • 链接器去库里找到 fputc.o → 拉进来。

    • fputc.o 又引用了 iusesemip.osemi.o → 一并拉入。

  • 成员粒度:库是以成员 .o 为单位拉取的。

    • 一个成员进来了,其中没被引用的 section 还能被“段级 GC”剔除。

    • fputc.o 必然触发半主机桩,因此关键段一定会留下。

  • 结果:Flash 被占用的不是 fputc 函数本身,而是它引入的半主机链条。

    3. .map 文件中的表现

    未重定向时

  • 交叉引用能看到:

  • 意味着:printf 最终依赖库版 fputc,触发半主机。

  • 改写 int fputc(int ch, FILE *f),例如用串口发送:

  • 已重定向时
    改写 int fputc(int ch, FILE *f),例如用串口发送:

    此时 fputc.osemi.oiusesemip.o 不再出现,Flash 立刻减少。

    4. Flash 体积减少的本质

  • Flash 变小 ≠ 只是少了一个 fputc,而是 少了一整条半主机链条

  • 这是一种 粗层次裁剪(成员没拉进来 → 整条依赖没触发),比单靠段级别 GC(细粒度裁剪)更有效。

  • 直观差别:

    • 没重定向fputc.o + semi.o + iusesemip.o → 几百到上千字节额外占用。

    • 重定向:只剩你自己的 fputc 几十字节。

  •  

    5. 常见问题和你的提问对应

  • Q:如果不定义串口,printf 默认去哪?
    → 默认走半主机输出(调试器终端),脱机会异常。

  • Q:map 里为什么会出现 semi.o
    → 因为库版 fputc 的实现强制依赖半主机符号,所以会被拉进来。

  • Q:是不是把整个 semi.o 都拉进来了?
    → 会拉整个成员,但链接器还能剔除其中没用的段;不过半主机必需段一定会保留。

  • Q:Flash 为什么能减小?
    → 不是 fputc 自身的大小,而是避免了一整条半主机依赖链。

    6. 图示对比

    未重定向


    已重定向


  • 7.注解


  • 1.什么是半主机?那有没有全主机?

  • 半主机 (Semihosting):名字里的“半”指的是:嵌入式程序运行在目标 MCU 上,但通过调试器(JTAG/SWD)把部分系统调用转发给 主机 来执行。

    • 常见功能:

      • fputc/printf → 把字符输出到主机调试窗口(最常用)。

      • 文件操作 → 在目标机上调用 fopen/fread/fwrite,其实是在 PC 上读写文件。

      • 时间查询、命令行参数等。

  • **“主机功能”**就是这些标准库 I/O 功能在 PC 机上的完整实现。因为嵌入式 MCU 上没有操作系统/文件系统,所以库默认通过 半主机转发 来模拟



  • 2.对于原始fputc来说,是不是不要半主机功能才认为之前是冗余代码?

1) 什么时候是“刚需”,什么时候是“冗余”

  • 需要半主机(调试时把 printf 输出到 IDE/主机终端、或要文件/时间等主机调用)
    → 库版 fputc 走半主机路径,这就必须把半主机运行时拉进来(semi.o 实现、iusesemip.o 特性桩、以及 stdout 流控制块等)。这些代码是功能所需,不是冗余。

  • 不需要半主机(比如量产固件,仅需 UART 打印)
    → 如果还用库版 fputc,链接器会把半主机链条拉进来,就变成对你的目标来说是冗余;这时改成自定义 fputc/__write,把这条链从根上剪掉,Flash 自然变小。

2) 为什么“会拉一条链”,而且看起来不止“打一行字那么简单”

  • printf 不直接发字节,它依赖一个下层钩子 fputc
    你当前这份 map 里,“未重定向”的典型形态是:printf* → fputc.o(库版),再由库版 fputc.o → iusesemip.o / semi.o 来启用半主机:
    另外很多 printf* 变体也会引用 stdout.o(.data)__stdout(标准输出流控制块),这是半主机/流式 I/O 的配套设施:

  • 半主机并不只有“输出一个字符”:库方需要提供与调试器通信的胶水代码(BKPT/SWI 调用路径)、错误/状态处理、流对象、以及(若启用)更广泛的 host 调用(文件、时间、命令行参数等)的“入口符号”。
    就算段级回收会剔除没被引用的函数,最小可用的那几段也要保留,所以体积不会是 0。你在 map 里也能看到链接器确实裁掉了很多没用段(“Removing Unused input sections … 489 unused section(s)”),但半主机必需段仍在

3) 为什么“自定义 fputc”会显著瘦身

  • 一旦你自带 fputc(比如发 UART),链接器的“未定义符号解析”就不再选择库的 fputc.o;既然 fputc.o 没被拉入,它对 iusesemip.o / semi.o 的后续依赖也根本不会触发

  • 这属于**成员级(粗粒度)**的裁剪:整条链没进门,比仅靠段级 GC(细粒度)更有效,Flash 体积自然下降。

  • 相反,如果你就是要半主机,那这条链必须存在,它不是“拖累”,而是功能所需的最小闭包

4) 一图看差别(同一工程,两种构建意图)

A. 需要半主机(调试到主机终端)

这些模块在你的 map 里都有证据可循:printf* → fputc.ofputc.o → iusesemip.o/semi.o、以及 __stdout 的引用。

B. 不要半主机(量产,仅 UART)

此时再看 map,半主机相关成员应当消失;如果还出现,就说明你某处仍走了库版路径。

5) 小结(给决策用)

  • 你要半主机semi.o/iusesemip.o 就是刚需,不是“拖累”;这时候体积是合理成本。

  • 你不要半主机 → 这些模块对你的目标来说就是冗余;重定向 fputc/__write(或使用只走内存的 vsnprintf + 自己发串)能把整条链剪掉,Flash 立减。

  • 进一步瘦身:避免 %f/%e/%g,否则还会把双精度格式化链拉进来(你 map 里 _fp_digits__aeabi_d* 那串成员就是这块体积)。

http://www.xdnf.cn/news/19321.html

相关文章:

  • 为什么计算机使用补码存储整数:补码的本质
  • 【秋招笔试】2025.08.29阿里云秋招笔试题
  • 【Linux】动静态库的制作与原理
  • 第三十二天:数组
  • 刷算法题-数组-02
  • 关于Ctrl+a不能全选的问题
  • Wi-Fi技术——OSI模型
  • VS安装 .NETFramework,Version=v4.6.x
  • React Hooks useMemo
  • [强网杯2019]随便注-----堆叠注入,预编译
  • centos7挂载iscis存储操作记录
  • postman 用于接口测试,举例
  • postman带Token测试接口
  • DAY50打卡
  • Redis 持久化 AOF 与 RDB 的区别
  • Ruoyi-vue-plus-5.x第二篇MyBatis-Plus数据持久层技术:2.1 MyBatis-Plus核心功能
  • audioLDM模型代码阅读(五)—— pipeline
  • Python学习大集合:基础与进阶、项目实践、系统与工具、Web 开发、测试与运维、人工智能(视频教程)
  • 电力电子技术知识学习-----晶闸管
  • VSCode中使用Markdown
  • 从零开始学炒股
  • cordova+umi 创建项目android APP
  • PythonDay42
  • KNN算法常见面试题
  • C数据结构:排序
  • 第25章学习笔记|额外的提示、技巧与技术(PowerShell 实战版)
  • Qt Core 之 QString
  • PyTorch 张量(Tensor)详解:从基础到实战
  • 【深度学习】配分函数:近似最大似然与替代准则
  • python复杂代码如何让ide自动推导提示内容