资讯动态

重定向C库部分函数的方法,包含printf、malloc、free、fopen等函数

发布时间:2026/9/14 11:15:35 来源:尧图企业网站定制
TL;DR本文介绍在 AC5、AC6、IAR 三种编译环境下如何实现printf的串口重定向与malloc/free内存管理函数的底层重定义并通过 LUA 解释器移植实战演示fputc注释与基础头文件修改的关键步骤最后对比三种编译器的实现差异与适用场景。目录一、printf 函数重定向二、malloc、free 内存管理重定义三、实战示例LUA 解释器移植四、三种编译器实现对比总结引言在嵌入式 MCU 的开发调试过程中通过串口打印运行过程中的一些信息往往比使用调试器更加高效便捷。尤其是在产品实际使用阶段通过对关键运行状态的实时输出能够为运行故障的判断与分析提供极具价值的依据。然而C 标准库中的printf、malloc、free等函数默认并不直接适配 MCU 的硬件环境需要开发者根据不同的编译工具链进行相应的重定向与重定义。本文将详细介绍在 AC5、AC6、IAR 三种主流编译环境下如何实现printf的串口重定向以及malloc、free等内存管理函数的底层重定义帮助读者在实际项目中快速落地。一、printf 函数重定向1. 原理说明printf本身并不具备实际的输出功能它只是一个格式化输出函数其作用是将数据按照指定的格式进行整理然后交给底层的输出设备。在标准 C 库中printf最终会调用fputc或_sys_write等底层接口来完成字符的实际发送。因此要实现串口打印核心思路就是重定向这些底层输出接口将原本送往标准输出设备的数据改送到串口外设上。在 STM32 等基于 HAL 库的开发中通常通过重写fputc函数并调用HAL_UART_Transmit来实现这一目标。2. AC5 编译器实现在使用 ARM Compiler 5AC5时如果工程没有勾选 MicroLIB微库则需要手动关闭半主机模式Semihosting否则程序在调试时可能会进入硬件错误。实现方式如下首先通过#pragma import(__use_no_semihosting)声明不使用半主机模式然后重写fputc函数在其中调用HAL_UART_Transmit将字符通过串口发送出去。示例代码如下#ifndef__MICROLIB//不勾选微库的话需要关闭半主从模式#pragmaimport(__use_no_semihosting)#endifintfputc(intch,FILE*f){//ch为要发送的字符returnHAL_UART_Transmit(huart1,(uint8_t*)ch,1,100)HAL_OK?ch:-1;}3. AC6 编译器实现ARM Compiler 6AC6的语法与 AC5 略有不同关闭半主机模式的方式改为使用内联汇编指令__asm(.global __use_no_semihosting\n\t)来声明。其余逻辑与 AC5 一致同样是重写fputc函数并调用HAL_UART_Transmit完成串口输出。示例代码如下#ifndef__MICROLIB//不勾选微库的话需要关闭半主从模式__asm(.global __use_no_semihosting\n\t)#endifintfputc(intch,FILE*f){//ch为要发送的字符returnHAL_UART_Transmit(huart1,(uint8_t*)ch,1,100)HAL_OK?ch:-1;}二、malloc、free 内存管理重定义1. 参数说明在使用malloc动态分配内存时需要特别注意其参数是所需动态分配的字节数而不是可以存储的元素个数。当动态分配的内存用于存储字符型数据时由于每个元素占用 1 字节因此所需字节数恰好等于需要存储的元素个数字符数 1用于存放字符串结束符。但如果存储的是整型或浮点型数据则字节数等于“需要存储的元素个数 × 一个元素的字节数”。例如分配 10 个int类型元素假设sizeof(int)4时应传入10 * 4 40字节而非10。2. AC5 编译器实现声明不使用 C 库的堆重定义 malloc、free、realloc、calloc映射到 OS 内存管理函数3. AC6 编译器实现使用 __asm 声明不使用 C 库的堆重定义内存管理函数映射到 OS 内存管理函数4. IAR 编译器实现通过 Linker Extra Options 重定向 malloc、free 到 OS 函数三、实战示例LUA 解释器移植在将 LUA 解释器移植到嵌入式 MCU 平台时源码内部会大量调用fopen、fread、fwrite等标准 C 库文件操作函数。此时如果仍然保留前面重定向的fputc实现会导致printf、fwrite等输出被错误地重定向到串口从而破坏 LUA 的文件读写逻辑。因此在移植 LUA 时需要将fputc注释掉避免干扰。1. 注释 fputc避免 printf 被错误重定向在 LUA 移植工程中将前面实现的fputc函数注释掉示例代码如下/* * 移植 LUA 时需注释掉 fputc * 避免 printf、fwrite 被错误重定向到串口 * 从而破坏 LUA 内部的文件读写逻辑。 */// int fputc(int ch, FILE *f){// //ch为要发送的字符// return HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 100)HAL_OK?ch:-1;// }2. 修改基础头文件的关键步骤LUA 源码中通过luaconf.h等基础头文件来适配不同平台。移植时主要修改以下几个关键点/* luaconf.h 关键修改点 *//* 1. 关闭 LUA 对标准 C 库文件操作的依赖改用自定义实现 */#defineLUA_USE_POSIX0/* 不使用 POSIX 文件接口 */#defineLUA_USE_DLOPEN0/* 不使用动态库加载 *//* 2. 指定 LUA 使用的整数与浮点类型匹配 MCU 的编译环境 */#defineLUA_INT_TYPELUA_INT_INT/* 使用 32 位 int */#defineLUA_FLOAT_TYPELUA_FLOAT_FLOAT/* 使用 32 位 float *//* 3. 关闭 LUA 的默认内存分配器改用 OS 提供的 malloc/free */#defineLUA_USE_CTYPE0/* 不使用 C 库的 ctype 函数 */3. 移植验证4. 常见问题与排查在 LUA 移植过程中开发者常会遇到以下几类问题下面逐一分析原因并给出排查步骤。问题一串口无输出原因分析fputc被注释掉后printf失去了底层输出接口导致串口没有任何打印信息或者HAL_UART_Transmit的句柄huart1与实际初始化的串口句柄不一致。解决步骤确认fputc是否被注释若已注释则printf不会输出到串口属正常现象检查HAL_UART_Transmit中传入的句柄是否与MX_USART1_UART_Init()初始化的句柄一致确认串口波特率、引脚复用配置是否正确可用逻辑分析仪或示波器验证 TX 引脚是否有波形。问题二程序进入 HardFault原因分析AC5/AC6 编译器未关闭半主机模式Semihosting程序在调试或运行时调用半主机相关函数触发硬件错误也可能是堆栈溢出导致。解决步骤确认是否已按前文方式关闭半主机模式AC5 用#pragma import(__use_no_semihosting)AC6 用__asm(.global __use_no_semihosting)检查是否勾选了 MicroLIB若已勾选则无需关闭半主机模式避免重复声明增大启动文件中的堆栈大小Stack_Size排除递归调用或局部大数组导致的栈溢出。问题三malloc 分配失败原因分析malloc未正确重定义到 OS 内存管理函数或堆空间Heap_Size设置过小无法满足 LUA 运行时的内存需求。解决步骤确认已按前文方式将malloc、free、realloc、calloc重定义到 OS 内存管理函数检查启动文件中Heap_Size是否足够LUA 解释器运行通常需要数 KB 到数十 KB 的堆空间建议适当调大在malloc返回NULL处添加断言或日志定位具体是哪个模块申请内存失败。问题四LUA 文件读写异常原因分析fputc未注释干净导致fwrite、printf等输出被错误重定向到串口破坏了 LUA 内部的文件读写逻辑或文件系统接口未正确适配。解决步骤确认fputc已完整注释且工程中不存在其他重定向fputc的实现检查luaconf.h中LUA_USE_POSIX是否已置 0避免 LUA 调用 POSIX 文件接口若使用 FatFS 等文件系统确认fopen、fread、fwrite已正确映射到文件系统提供的接口。完成上述修改后重新编译工程。实测仅修改基础头文件即可完成 LUA 的基础移植LUA 解释器可以正常运行且串口打印与文件读写互不干扰。四、三种编译器实现对比下表从实现方式、关键代码、注意事项与适用场景四个维度对 AC5、AC6、IAR 三种编译器在printf重定向与malloc/free重定义上的差异进行汇总便于读者在实际项目中快速选型。对比维度AC5ARM Compiler 5AC6ARM Compiler 6IARprintf 重定向实现方式通过#pragma import(__use_no_semihosting)关闭半主机模式重写fputc调用HAL_UART_Transmit通过内联汇编__asm(.global __use_no_semihosting)关闭半主机模式重写fputc调用HAL_UART_Transmit通过 Linker Extra Options 配置重定向重写fputc或使用底层__write接口printf 关键代码#pragma import(__use_no_semihosting)int fputc(int ch, FILE *f)__asm(.global __use_no_semihosting)int fputc(int ch, FILE *f)在链接选项中指定fputc重定向函数体内调用串口发送接口malloc/free 重定义方式声明不使用 C 库的堆重定义malloc、free、realloc、calloc映射到 OS 内存管理函数使用__asm声明不使用 C 库的堆重定义内存管理函数映射到 OS 内存管理函数通过 Linker Extra Options 将malloc、free重定向到 OS 函数malloc/free 关键代码#pragma import(__use_no_heap) 自定义malloc/free实现__asm(.global __use_no_heap) 自定义malloc/free实现链接配置中指定__iar_alloc或映射到 OS 内存管理接口注意事项未勾选 MicroLIB 时必须关闭半主机模式否则调试时可能进入硬件错误语法与 AC5 不同需使用内联汇编声明同样需注意 MicroLIB 勾选状态需在工程链接选项中正确配置重定向避免与标准库默认实现冲突适用场景传统 Keil MDK 工程、老项目维护、对编译速度要求较高的场景新项目开发、需要更高优化等级与更现代编译特性的场景使用 IAR Embedded Workbench 的团队、对代码体积有严格要求的场景总结回顾 printf 重定向与 malloc/free 重定义的关键要点不同编译器AC5/AC6/IAR的实现差异与共性推荐通用性 MCU 架构 LsSDK支持多平台与多 OS

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价