将带有 args 的 printf 用于可变参数函数?

Use printf with args into variadic functions?

我需要一个功能类似于 printf 的函数,但对 fmt 字符串进行了一些更改:例如,在开头添加一个包含日期时间的字符串,但其余的,我会保持相同的 printf 内容...

void simple_printf(const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    va_end(args);

    /* made some changes to fmt, concatenate string,...*/

    printf(fmt, ...);
}

这是我正在编写的代码。如您所见,我希望更改 fmt 字符串,但之后调用 'standard' printfsprintf,传递参数 — 一种绕过。

这可能吗?

有多种方法可以做到这一点。您可以在我的 SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq 子目录中找到一些执行此类操作的代码。这是我多年来开发的一个包(最早的版本我仍然有日期到 1988 年的记录)并且我在我的大部分 C 程序中使用它。

现在使用的方案通过将要格式化的数据转换为字符串来确保只有一个写入操作——参见err_fmtmsg()——然后使用适当的写入机制(标准I/O,例如fprintf(),或 write(), or syslog()) 将消息发送到输出机制。

static size_t err_fmtmsg(char *buffer, size_t buflen, int flags, int errnum,
                         const char *format, va_list args)
{
    char *curpos = buffer;
    char *bufend = buffer + buflen;

    buffer[0] = '[=10=]';   /* Not strictly necessary */
    if ((flags & ERR_NOARG0) == 0)
        curpos = efmt_string(curpos, bufend, "%s: ", arg0);
    if (flags & ERR_LOGTIME)
    {   
        char timbuf[32];
        curpos = efmt_string(curpos, bufend,
                             "%s - ", err_time(flags, timbuf, sizeof(timbuf)));
    }   
    if (flags & ERR_PID)
        curpos = efmt_string(curpos, bufend,
                             "pid=%d: ", (int)getpid());
    curpos = vfmt_string(curpos, bufend, format, args);
    if (flags & ERR_ERRNO)
        curpos = efmt_string(curpos, bufend,
                             "error (%d) %s\n", errnum, strerror(errnum));
    assert(curpos >= buffer);
    return((size_t)(curpos - buffer));
}

如你所见,这可以在arg0生成的消息前加前缀(程序名,通过函数设置err_setarg0();它可以添加一个PID;它可以添加时间戳(在标志的控制下有整数秒、毫秒、微秒、纳秒的选项),也可以附加错误号和相应的系统错误消息。

这是一个隐藏在系统内部的功能。在外部级别,入口点之一是 extern void err_syserr(const char *fmt, ...); — 这会自动添加系统错误,在标准错误上打印消息,然后退出程序。还有各种各样的其他日志记录入口点,其中一些是出口点,一些是 return。还有很多控件。

请注意 err_fmtmsg() 函数接受一个参数 va_list args。这通常是最好的工作方式。您应该使用此方案编写代码:

void simple_printf(const char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    simple_vprintf(fmt, args);
    va_end(args);
}

void simple_vprintf(const char* fmt, va_list args)
{
    /* … preamble … */
    vprintf(fmt, args);
    /* … postamble … */
}

您使用 va_list 函数编写主函数,并使用调用主函数的 ... 提供一个方便的接口,如上所示。

如果您打算多次调用标准 I/O 编写函数(fprintf() 等),请考虑使用 flockfile()funlockfile() 以保持输出 'atomic'.

void simple_vprintf(const char* fmt, va_list args)
{
    flockfile(stdout);
    /* … preamble — possibly writing to stdout … */
    vprintf(fmt, args);
    /* … postamble — possibly writing to stdout … */
    funlockfile(stdout);
}

您还可以考虑提供如下功能:

extern void simple_fprintf(FILE *fp, const char *fmt, ...);
extern void simple_vfprintf(FILE *fp, const char *fmt, va_list args);

然后 simple_vprintf() 将简单地调用 simple_vfprintf(stdout, fmt, args)。然后您可能会开始查看头文件中覆盖函数的 static inline 实现。

一段时间后,如果您实现了足够多的变体,您就会开始侵占 stderr.[ch] 中的实现。

您应该从可变参数函数中调用 vprintf,而不是调用 printf

#include <stdarg.h>
#include <stdio.h>

void simple_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    time_t t = time(NULL);
    int d = (t - 746755200) / 86400;
    int h = (t %= 86400) / 3600;
    int m = (t %= 3600) / 60;
    int s = t % 60;

    /* output a timestamp at the beginning of the line, thank you Janice Brandt */
    printf("Sep %d, 1993 %02d:%02d:%02d UTC: ", d, h, m, s);

    /* no changes should be made to fmt because it is a constant string */
    vprintf(fmt, args);
    va_end(args);
}