是否有格式处理器来编写我自己的类似 printf 的函数并保留 %d 样式参数,而不使用 sprintf?

如何解决是否有格式处理器来编写我自己的类似 printf 的函数并保留 %d 样式参数,而不使用 sprintf?

我正在为 MCU 编写串行接口,我想知道如何创建类似 printf 的函数来写入串行 UART。我可以写入 UART,但为了节省内存和堆栈空间,并避免临时字符串缓冲区,我更愿意直接写入而不是将 sprintf() 写入字符串,然后通过串行写入字符串。没有内核,也没有文件处理,因此 FILE* 像来自 fprintf() 的那些写入将不起作用(但 sprintf() 可以)。

是否有一些东西可以为每个字符处理格式化的字符串,以便我可以在解析格式字符串并应用相关参数时逐个字符地打印?

解决方法

标准 C printf 函数系列没有“打印到字符回调”类型的功能。大多数嵌入式平台也不支持 fprintf

首先尝试为您的平台挖掘 C 运行时,它可能有一个内置的解决方案。例如,ESP-IDF 有 ets_install_putc1(),它实质上安装了 printf 的回调(尽管它的 ets_printf 已经打印到 UART0)。

否则,还有其他 printf 实现是专门为嵌入式应用设计的,您可以根据自己的需要进行调整。

例如 mpaland/printf 有一个函数将字符打印机回调作为第一个参数:

int fctprintf(void (*out)(char character,void* arg),void* arg,const char* format,...);

另请参阅此相关问题:Minimal implementation of sprintf or printf

,

你[在你的最高评论中]说过你有 GNU,所以 fopencookie 用于钩子 [我以前成功地使用过它]。

附加到 stdout 可能很棘手,但可行。

注意我们有:FILE *stdout;(即它[只是]一个指针)。因此,只需将其设置为 [newly] 打开的流应该可以工作。

所以,我认为你可以做到,要么(1):

FILE *saved_stdout = stdout;

或(2):

fclose(stdout);

那么,(3):

FILE *fc = fopencookie(...);
setlinebuf(fc);  // and whatever else ...
stdout = fc;

您可以[可能]调整顺序以适应(例如先执行fclose等)

我曾寻找过类似于 freopenfdopen 的东西以适合您的情况,但我没有找到任何东西,因此可能选择执行 stdout = ...;

这很好如果没有有任何试图直接写入 fd 1 的代码(例如 write(1,"hello\n",6);)。

即使在那种情况下,也可能有办法。


更新:

你知道 FILE*stdout 是否是一个常量吗?如果是这样,我可能需要做一些疯狂的事情,比如 FILE **p = &stdout 然后 *p = fopencookie(...)

你的担心是对的,但不是,正如你所想的那样。继续阅读...


stdout 是可写的但是 ...

在我发布之前,我检查了 stdio.h,它有:

extern FILE *stdout;        /* Standard output stream.  */

仔细想想,stdout 必须是可写的。

否则,我们永远做不到:

fprintf(stdout,"hello world\n");
fflush(stdout);

另外,如果我们做了一个 fork,那么[在子进程中]如果我们想设置 stdout 去访问一个日志文件,我们需要能够做到:>

freopen("child_logfile","w",stdout);

所以,不用担心......


信任但验证 ...

我说“不用担心”了吗?我可能为时过早;-)

一个问题。

这是一个示例测试程序:

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#if 1 || DEBUG
#define dbgprt(_fmt...) \
    do { \
        fprintf(stderr,_fmt); \
        fflush(stderr); \
    } while (0)
#else
#define dbgprt(_fmt...) \
    do { } while (0)
#endif

typedef struct {
    int ioport;
} uartio_t;

char *arg = "argument";

ssize_t
my_write(void *cookie,const char *buf,size_t len)
{
    uartio_t *uart = cookie;
    ssize_t err;

    dbgprt("my_write: ENTER ioport=%d buf=%p len=%zu\n",uart->ioport,buf,len);

    err = write(uart->ioport,len);

    dbgprt("my_write: EXIT err=%zd\n",err);

    return err;
}

int
my_close(void *cookie)
{
    uartio_t *uart = cookie;

    dbgprt("my_close: ioport=%d\n",uart->ioport);
    int err = close(uart->ioport);
    uart->ioport = -1;

    return err;
}

int
main(void)
{

    cookie_io_functions_t cookie = {
        .write = my_write,.close = my_close
    };
    uartio_t uart;

    printf("hello\n");
    fflush(stdout);

    uart.ioport = open("uart",O_WRONLY | O_TRUNC | O_CREAT,0644);
    FILE *fc = fopencookie(&uart,cookie);

    FILE *saved_stdout = stdout;
    stdout = fc;

    printf("uart simple printf\n");
    fprintf(stdout,"uart fprintf\n");
    printf("uart printf with %s\n",arg);

    fclose(fc);
    stdout = saved_stdout;

    printf("world\n");

    return 0;
}

程序输出:

编译后运行:

./uart >out 2>err

这应该会产生预期的结果。 但是,我们得到(来自head -100 out err uart):

==> out <==
hello
uart simple printf
world

==> err <==
my_write: ENTER ioport=3 buf=0xa90390 len=39
my_write: EXIT err=39
my_close: ioport=3

==> uart <==
uart fprintf
uart printf with argument

哇!发生了什么? out 文件应该

hello
world

而且,uart 文件应该行而不是两行

uart printf
uart simple printf
uart printf with argument

但是,uart simple printf 行转到 out 而不是 [预期的] uart 文件。

再次,哇!发生了什么?!?!


说明:

程序是用 gcc 编译的。使用 clang 重新编译会产生所需的结果!

事实证明,gcc 试图有帮助。编译的时候就转换了:

printf("uart simple printf\n");

进入:

puts("uart simple printf");

如果我们反汇编可执行文件[或使用 -S 编译并查看 .s 文件],我们会看到。

puts 函数[显然]绕过 stdout 并使用 glibc 的内部版本:_IO_stdout

似乎 glibc 的 puts_IO_puts弱别名并且使用了 _IO_stdout

_IO_* 符号不能直接访问。它们是 glibc 所谓的“隐藏”符号——仅对 glibc.so 本身可用。


真正的解决办法:

经过大量的黑客攻击后,我发现了这一点。这些尝试/修复在下面的附录中。

事实证明,glibc 将(例如)stdout 定义为:

FILE *stdout = (FILE *) &_IO_2_1_stdout_;

在内部,glibc 使用该内部名称。因此,如果我们更改 stdout 指向的内容,它会破坏这种关联。

实际上,只有_IO_stdout是隐藏的。版本化符号全局的,但我们必须知道名称,要么从 readelf 输出或使用一些 __GLIBC_* 宏(即有点混乱) .

因此,我们需要修改保存/恢复代码以更改 stdout 中的值,而是将 memcpy 中的值更改为 stdout 指向

所以,在某种程度上,是对的。它 [有效] const [只读]​​。

因此,对于上述示例/测试程序,当我们要设置一个新的 stdout 时,我们需要:

FILE *fc = fopencookie(...);
FILE saved_stdout = *stdout;
*stdout = *fc;

当我们想恢复原来的时候:

*fc = *stdout;
fclose(fc);
*stdout = saved_stdout;

所以,问题真的不是gcc。我们开发的原始保存/恢复不正确。但是,它是潜伏的。只有当 gcc 调用 puts 时,错误才会出现。

个人笔记:啊哈!现在我让这段代码工作了,它似乎很熟悉。我有一种似曾相识的体验。我很确定我过去也必须这样做。但是,太久了,我已经完全忘记了。


半有效但更复杂的变通方法/修复:

注意:如前所述,这些变通方法仅用于展示我在之前尝试过的方法,以找到上面的简单修复方法。

一种解决方法是禁用 gccprintfputs 的转换。

最简单的方法可能是[如前所述]用clang编译。但是,有些网页说 clanggcc 的作用相同。它不会对我的 puts 版本进行clang优化 [for x86_64]: 7.0.1 -- YMMV

对于gcc ...

一个简单的方法是用 -fno-builtins 编译。这修复了 printf->puts 问题,但禁用了 memcpy 等的 [理想] 优化。它也未记录 [AFAICT]

另一种方法是强制我们自己的版本的 puts 调用 fputs/fputc。我们会将其放入(例如)puts.c 中,然后对其进行构建和链接:

#include <stdio.h>

int
puts(const char *str)
{
    fputs(str,stdout);
    fputc('\n',stdout);
}

当我们刚刚这样做时:stdout = fc; 我们欺骗 glibc 有点[实际上,glibc 有点欺骗了我们],这已​​经现在回来困扰我们。

“干净”的方法是做 freopen。但是,AFAICT,没有 类似的功能适用于 cookie 流。 可能有,但我没找到。

因此,“脏”方法之一可能是唯一的方法。我认为使用上面的“自定义”puts 函数方法是最好的选择。

编辑:在我重读上面的“欺骗”句子后,我找到了简单的解决方案(即它让我更深入地研究了 glibc 源)。

,

根据您的标准库实现,您需要编写自己的 fputc_write 函数版本。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-