简体   繁体   English

是否有格式处理器可以编写我自己的类似 printf 的 function 并保持 %d 样式 arguments,而不使用 sprintf?

[英]Is there a format processor to write my own printf-like function and keep the %d style arguments, without using sprintf?

I'm writing a serial interface for an MCU, and I want to know how one would create a printf -like function to write to the serial UART.我正在为 MCU 编写串行接口,我想知道如何创建printf类似 function 来写入串行 UART。 I can write to the UART, but to save memory and stack space, and avoid temp string buffers, I would prefer to do that write directly instead of doing sprintf() to a string and then writing the string via serial.我可以写入 UART,但为了节省 memory 和堆栈空间,并避免临时字符串缓冲区,我宁愿直接写入而不是sprintf()写入字符串,然后通过串行写入字符串。 There is no kernel and no file handling, so FILE* writes like those from fprintf() won't work (but sprintf() does).没有 kernel 也没有文件处理,所以像fprintf()那样的FILE*写入将不起作用(但sprintf()可以)。

Is there something that processes formatted strings for each char, so I can print char-by-char as it parses the format string, and applies the related arguments?是否有一些东西可以处理每个字符的格式化字符串,所以我可以在解析格式字符串时逐个打印字符,并应用相关的 arguments?

Standard C printf family of functions don't have a "print to a character callback" type of functionality.标准 C printf系列函数没有“打印到字符回调”类型的功能。 Most embedded platforms don't support fprintf either.大多数嵌入式平台也不支持fprintf

First try digging around the C runtime for your platform, it might have a built-in solution.首先尝试为您的平台挖掘 C 运行时,它可能有一个内置的解决方案。 For example, ESP-IDF has ets_install_putc1() which essentially installs a callback for printf (though its ets_printf already prints to UART0).例如,ESP-IDF 有ets_install_putc1() ,它本质上为printf安装了回调(尽管它的ets_printf已经打印到 UART0)。

Failing that, there are alternative printf implementations designed specifically for embedded applications which you can adapt to your needs.如果做不到这一点,还有专门为嵌入式应用程序设计的替代printf实现,您可以根据自己的需要进行调整。

For example mpaland/printf has a function taking the character printer callback as the first argument:例如mpaland/printf有一个 function 将字符打印机回调作为第一个参数:

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

Also see this related question: Minimal implementation of sprintf or printf .另请参阅此相关问题: sprintf 或 printf 的最小实现

You had said [in your top comments] that you had GNU, so fopencookie for the hooks [I've used it before with success].你曾说过[在你的顶级评论中]你有 GNU,所以fopencookie用于钩子 [我以前成功地使用过它]。

Attaching to stdout may be tricky, but doable.附加到stdout可能很棘手,但可行。

Note that we have: FILE *stdout;请注意,我们有: FILE *stdout; (ie it's [just] a pointer ). (即它[只是]一个指针)。 So, simply setting it to the [newly] opened stream should work.因此,只需将其设置为 [新] 打开的 stream 即可。

So, I think you can do, either (1):所以,我认为你可以做,或者(1):

FILE *saved_stdout = stdout;

Or (2):或(2):

fclose(stdout);

Then, (3):然后,(3):

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

You can [probably] adjust the order to suit (eg doing fclose first, etc.)您可以[可能]调整顺序以适应(例如先执行fclose等)

I had looked for something analogous to freopen or fdopen to fit your situation, but I didn't find anything, so doing stdout =...;我一直在寻找类似于freopenfdopen的东西来适应你的情况,但我没有找到任何东西,所以做stdout =...; may be the option.可能是选择。

This works fine if you do not have any code that tries to write to fd 1 directly (eg write(1,"hello\n",6); ).如果没有任何尝试直接写入 fd 1 的代码(例如write(1,"hello\n",6); ),这可以正常工作。

Even in that case, there is probably a way.即使在那种情况下,也可能有办法。


UPDATE:更新:

Do you know if FILE*stdout is a const?你知道 FILE*stdout 是否是一个常量吗? If so, I might need to do something crazy like FILE **p = &stdout and then *p = fopencookie(...)如果是这样,我可能需要做一些疯狂的事情,比如 FILE **p = &stdout 然后 *p = fopencookie(...)

You were right to be concerned, but not for quite the reason you think.你的担心是对的,但并不是你想的那样。 Read on...继续阅读...


stdout is writable but ... stdout是可写的,但是......

Before I posted, I checked stdio.h , and it has:在发布之前,我检查了stdio.h ,它有:

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

If you think about it, stdout must be writable.如果您考虑一下, stdout必须是可写的。

Otherwise, we could never do:否则,我们永远无法做到:

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

Also, if we did a fork , then [in the child] if we wanted to set up stdout to go to a logfile, we'd need to be able to do:此外,如果我们做了一个fork ,那么 [in the child] 如果我们想将stdout设置为 go 到日志文件,我们需要能够做到:

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

So, no worries...所以,不用担心...


Trust but verify ...信任但验证...

Did I say "no worries"?我说“不用担心”吗? I may have been premature;-)我可能还为时过早;-)

There is an issue.一个问题。

Here is a sample test program:这是一个示例测试程序:

#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;
}

Program output:程序 output:

After compiling, running with:编译后,运行:

./uart >out 2>err

This should produce an expected result.这应该会产生预期的结果。 But , we get (from head -100 out err uart ):但是,我们得到(从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

Whoa?哇? What happened?发生了什么? The out file should just be: out文件应该是:

hello
world

And, the uart file should have three lines instead of two :而且, uart文件应该三行而不是行:

uart printf
uart simple printf
uart printf with argument

But, the uart simple printf line went to out instead of [the intended] uart file.但是, uart simple printf out代替了[预期的] uart文件。

Again, whoa,?再次,哇,? what happened?!?!发生了什么?!?!


Explanation:解释:

The program was compiled with gcc .该程序是用gcc编译的。 Recompiling with clang produces the desired results!使用clang编译会产生所需的结果!

It turns out that gcc was trying to be too helpful.事实证明, gcc试图提供太多帮助。 When compiling, it converted:编译时,它转换为:

printf("uart simple printf\n");

Into:进入:

puts("uart simple printf");

We see that if we disassemble the executable [or compile with -S and look at the .s file].如果我们反汇编可执行文件 [或使用-S编译并查看.s文件],我们会看到这一点。

The puts function [apparently] bypasses stdout and uses glibc's internal version: _IO_stdout . puts function [显然]绕过stdout并使用 glibc 的内部版本: _IO_stdout

It appears that glibc's puts is a weak alias to _IO_puts and that uses _IO_stdout .似乎 glibc 的puts_IO_puts弱别名,并且使用_IO_stdout

The _IO_* symbols are not directly accessible. _IO_*符号不可直接访问。 They're what glibc calls "hidden" symbols--available only to glibc.so itself.它们是 glibc 所称的“隐藏”符号——仅对glibc.so本身可用。


The real fix:真正的修复:

I discovered this after considerable hacking around.经过大量的黑客攻击后,我发现了这一点。 Those attempts/fixes are in an appendix below.这些尝试/修复在下面的附录中。

It turns out that glibc defines (eg) stdout as:事实证明, glibc将(例如) stdout定义为:

FILE *stdout = (FILE *) &_IO_2_1_stdout_;

Internally, glibc uses that internal name.在内部, glibc使用该内部名称。 So, if we change what stdout points to, it breaks that association.因此,如果我们更改stdout指向的内容,它就会破坏这种关联。

In actual fact, only _IO_stdout is hidden.实际上,只有_IO_stdout是隐藏的。 The versioned symbol is global but we have to know the name either from readelf output or by using some __GLIBC_* macros (ie a bit messy).版本化符号全局的,但我们必须从readelf output 或使用一些__GLIBC_*宏(即有点混乱)知道名称。

So, we need to modify the save/restore code to not change the value in stdout but memcpy to/from what stdout points to.因此,我们需要修改保存/恢复代码以更改stdout中的值,而是将memcpy更改为/从stdout指向的内容。

So, in a way, you were correct.所以,在某种程度上,是对的。 It is [effectively] const [readonly].它是[有效地] const [只读]。

So, for the above sample/test program, when we want to set a new stdout , we want:所以,对于上面的示例/测试程序,当我们想要设置一个新的stdout时,我们想要:

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

When we want to restore the original:当我们要恢复原状时:

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

So, it really wasn't gcc that was the issue.所以,问题真的不是gcc The original save/restore we developed was incorrect.我们开发的原始保存/恢复不正确。 But, it was latent.但是,它是潜伏的。 Only when gcc called puts did the bug manifest itself.只有当gcc调用puts时,错误才会显现出来。

Personal note: Aha, Now that I got this code working.个人说明:啊哈,现在我得到了这段代码。 it seems oddly familiar.它似乎奇怪地熟悉。 I'm having a deja vu experience.我有似曾相识的经历。 I'm pretty sure that I've had to do the same in the past.我很确定我过去也必须这样做。 But, it was so long ago, that I had completely forgotten about it.但是,那是很久以前的事了,我已经完全忘记了。


Workarounds / fixes that semi-worked but are more complex:半有效但更复杂的解决方法/修复:

Note: As mentioned, these workarounds are only to show what I tried before finding the simple fix above.注意:如前所述,这些解决方法只是为了展示我在找到上述简单修复之前尝试过的内容。

One workaround is to disable gcc 's conversion from printf to puts .一种解决方法是禁用gccprintfputs的转换。

The simplest way may be to [as mentioned] compile with clang .最简单的方法可能是 [如前所述] 使用clang编译。 But, some web pages say that clang does the same thing as gcc .但是,一些 web 页面说gccclang做同样的事情。 It does not do the puts optimization on my version of clang [for x86_64 ]: 7.0.1 -- YMMV没有对我的clang [for x86_64 ] 版本进行puts优化:7.0.1 -- YMMV

For gcc ...对于gcc ...

A simple way is to compile with -fno-builtins .一种简单的方法是使用-fno-builtins进行编译。 This fixes the printf->puts issue but disables [desirable] optimizations for memcpy , etc. It's also undocumented [AFAICT]这修复了printf->puts问题,但禁用了memcpy等的 [desirable] 优化。它也没有记录[AFAICT]

Another way is to force our own version of puts that calls fputs/fputc .另一种方法是强制我们自己调用fputs/fputcputs版本。 We'd put that in (eg) puts.c and build and link against it:我们将其放入(例如) puts.c并构建和链接它:

#include <stdio.h>

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

When we just did: stdout = fc;当我们刚刚这样做时: stdout = fc; we were deceiving glibc a bit [actually, glibc was deceiving us a bit] and that has now come back to haunt us.我们有点欺骗glibc [实际上,glibc 有点欺骗了我们],现在又回来困扰我们了。

The "clean" way would be to do freopen . “干净”的方法是做freopen But, AFAICT, there is no analogous function that works on a cookie stream.但是,AFAICT,没有类似的 function 可用于 cookie stream。 There may be one, but I haven't found it.可能有,但我没找到。

So, one of the "dirty" methods may be the only way.因此,其中一种“肮脏”的方法可能是唯一的方法。 I think using the "custom" puts function method above would be the best bet.我认为使用上面的“自定义” puts function 方法是最好的选择。

Edit: It was after I reread the above "deceiving" sentence that I hit on the simple solution (ie It made me dig deeper into glibc source).编辑:在我重读上面的“欺骗”句子之后,我找到了简单的解决方案(即它让我更深入地研究了glibc源代码)。

depending on your standard library implementation you need to write your own versions of fputc or _write functions.根据您的标准库实现,您需要编写自己的fputc_write函数版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将variadic模板参数转发到类似printf的函数 - Forwarding variadic template parameter to printf-like function 在 C++ 中抑制类似 printf 的警告 function - Suppressing warnings for a printf-like function in C++ Python RE-用于将类似printf的格式字符串与转义引号匹配的正则表达式 - Python RE - Regular expression for matching a printf-like format string with escaped quotation marks 使用%ld格式字符串而不是int数据类型的%d使用sprintf / printf的效果 - effect of using sprintf / printf using %ld format string instead of %d with int data type std::string 格式如 sprintf / printf 并且也不允许 arguments - std::string formatting like sprintf / printf and also allow for no arguments 如何使用具有类似 printf 格式的 C++ std::ostream? - How to use C++ std::ostream with printf-like formatting? 编写c ++函数format_string用于格式化std :: string的sprintf - write c++ function format_string for formatting like sprintf of std::string 如何将可变数量的参数传递给printf / sprintf - How to pass variable number of arguments to printf/sprintf 没有类似格式说明符的c ++中的printf实用程序? - printf like utility in c++ without format specifier? 如何在 clang 格式的函数参数包装和对齐上强加我自己的自定义样式? - How can I impose my own custom style on function parameter wrapping and alignment in clang-format?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM