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

C++ 是否有一个格式处理器来编写我自己的类似printf的函数并保留%d样式的参数,而不使用sprintf?,c++,c,serial-port,printf,uart,C++,C,Serial Port,Printf,Uart,我正在为MCU编写一个串行接口,我想知道如何创建一个类似于printf的函数来写入串行UART。我可以写入UART,但为了节省内存和堆栈空间,避免临时字符串缓冲区,我宁愿直接写入,而不是对字符串执行sprintf(),然后通过串行方式写入字符串。没有内核,也没有文件处理,因此像fprintf()中那样的file*写入将无法工作(但是sprintf()可以) 有没有什么东西可以处理每个字符的格式化字符串,这样我就可以在它解析格式化字符串时一个字符一个字符地打印出来,并应用相关参数?根据标准库实现,

我正在为MCU编写一个串行接口,我想知道如何创建一个类似于printf的函数来写入串行UART。我可以写入UART,但为了节省内存和堆栈空间,避免临时字符串缓冲区,我宁愿直接写入,而不是对字符串执行
sprintf()
,然后通过串行方式写入字符串。没有内核,也没有文件处理,因此像
fprintf()
中那样的
file*
写入将无法工作(但是
sprintf()
可以)


有没有什么东西可以处理每个字符的格式化字符串,这样我就可以在它解析格式化字符串时一个字符一个字符地打印出来,并应用相关参数?

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

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

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

如果做不到这一点,就有专门为嵌入式应用程序设计的替代
printf
实现,您可以根据自己的需要进行调整

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

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

另请参见此相关问题:.

您在顶部评论中说您拥有GNU,因此,
fopencookie
用于挂钩[我以前成功地使用过它]

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

注意,我们有:
文件*stdout(即,它只是一个指针)。因此,只需将其设置为[新]打开的流就可以了

因此,我认为你可以做(1):

或(二):

然后,(3):

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

我找了一些类似于
freopen
fdopen
的东西来适应你的情况,但我什么也没找到,所以做
stdout=可能是选项

如果您没有任何试图直接写入fd 1的代码(例如
write(1,“hello\n”,6);
),则此功能可以正常工作

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


更新:

你知道FILE*stdout是否是常量吗?如果是这样,我可能需要做一些疯狂的事情,比如文件**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,buf,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,"w",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;
}
这应该会产生预期的结果。但是,我们得到(从
头-100输出错误uart
):

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

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

又一次,哇!,发生什么事了


说明:

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

结果证明,
gcc
试图提供太多帮助。编译时,它转换为:

printf("uart simple printf\n");
进入:

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

put
函数[显然]绕过
stdout
,使用glibc的内部版本:
\u IO\u stdout

glibc的
put
似乎是
\u IO\u put
的一个弱别名,它使用
\u IO\u stdout

无法直接访问
\u IO*
符号。它们是glibc称之为“隐藏”符号的东西——只对glibc可用


真正的解决方案:

这是我经过多次黑客攻击后发现的。这些尝试/修复在下面的附录中

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

FILE *stdout = (FILE *) &_IO_2_1_stdout_;
在内部,
glibc
使用该内部名称。因此,如果我们更改stdout
所指向的内容,它就会破坏这种关联

实际上,只有
\u IO\u stdout
是隐藏的。版本符号是全局的,但我们必须从
readelf
输出或使用一些
\uu GLIBC.*
宏来知道名称(即有点混乱)

因此,我们需要修改save/restore代码,以不更改
stdout
中的值,而是将
memcpy
中的值更改为
stdout
指向的值

所以,在某种程度上,你是对的。它是[有效地]
const
[readonly]

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

FILE *fc = fopencookie(...);
FILE saved_stdout = *stdout;
*stdout = *fc;
当我们要恢复原始文件时:

*fc = *stdout;
fclose(fc);
*stdout = saved_stdout;
因此,问题并不是在于gcc。我们开发的原始保存/恢复不正确。但是,它是潜在的。只有当调用
gcc
时,错误才会显现出来

个人提示:啊哈!现在我让这个代码运行起来了,它是se
==> 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
hello
world
uart printf
uart simple printf
uart printf with argument
printf("uart simple printf\n");
puts("uart simple printf");
FILE *stdout = (FILE *) &_IO_2_1_stdout_;
FILE *fc = fopencookie(...);
FILE saved_stdout = *stdout;
*stdout = *fc;
*fc = *stdout;
fclose(fc);
*stdout = saved_stdout;
#include <stdio.h>

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