问题 重复使用可变参数函数参数不起作用


我有一个函数,试图将东西记录到控制台和日志文件,但它不起作用。第二次使用可变长度参数会将垃圾写入控制台。有任何想法吗?

    void logPrintf(const char *fmt, ...) {
        va_list ap;    // log to logfile
        va_start(ap, fmt);
        logOpen;
        vfprintf(flog, fmt, ap);
        logClose;
        va_end(ap);
        va_list ap2;   // log to console
        va_start(ap2, fmt);
        printf(fmt, ap2);
        va_end(ap2);
    }

5425
2018-02-16 10:07


起源

你需要第二次使用vprintf,而不是printf。 - Greg Prisament


答案:


原始代码失败,因为它试图使用 printf() 需要使用的地方 vprintf()。采取可疑的点,如 logOpen 和 logClose 面值的陈述(给出符号,可能是它们是打开和关闭的宏 flog 文件流),代码应该是:

void logPrintf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    logOpen;
    vfprintf(flog, fmt, ap);
    logClose;
    va_end(ap);
    va_list ap2;
    va_start(ap2, fmt);
    vprintf(fmt, ap2);
    va_end(ap2);
}

没有特别要求使用两个单独的 va_list 变量;完全可以使用相同的两次 只要你使用 va_end() 在你使用之前 va_start() 再次。

void logPrintf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    logOpen;
    vfprintf(flog, fmt, ap);
    logClose;
    va_end(ap);
    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);
}

当一个 va_list 值被传递给另一个函数(vfprintf() 和 vprintf() 在此代码中),您应该假设它在当前函数中不再可用。打电话是安全的 va_end() 在上面。

没有必要 va_copy() 在这段代码中。它有效,但不需要它。你需要 va_copy() 在其他情况下,例如当您的函数被传递时 va_list 你需要处理两次列表:

void logVprintf(const char *fmt, va_list args1)
{
    va_list args2;
    va_copy(args2, args1);
    logOpen;
    vfprintf(flog, fmt, args1);
    logClose;
    vprintf(fmt, args2);
    va_end(args2);
}

请注意,在此代码中,调用代码是调用的责任 va_end() 上 args1。的确,标准说:

每次调用 va_start 和 va_copy 宏   应与相应的调用相匹配 va_end 宏在同一个功能。

自从 logVprintf() 函数也不会调用 va_start 要么 va_copy 初始化 args1,它不能合法地打电话 va_end 上 args1。另一方面,标准要求它打电话 va_end 对于 args2

logPrintf() 功能可以实现 logVprintf() 现在:

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

这种结构 - 一种需要的操作功能 va_list 和一个封面函数,它接受省略号(变量参数)并在转换为a后将它们传递给操作函数 va_list  - 通常是一种很好的工作方式。迟早,你通常会发现需要带有的版本 va_list 论据。


10
2017-12-31 21:01



这是正确的答案,直接解决OP的问题,而不建议改变他们的架构。 - Vortico


升级你的编译器,更像是C ++:

template <typename... Args>
void logPrintf(const char *fmt, Args&&... args) {
    logOpen;
    fprintf(flog, fmt, args...);
    logClose;

    printf(fmt, args...);
}

虽然当然提供类型安全版本会很好 printf 和 fprintf


2
2018-02-16 11:17



以上示例需要C ++ 11。 - David Given
@DavidGiven:是的(因此 升级你的编译器)。 - Matthieu M.
我认为vfprintf需要在这里改为fprintf - 你传递的是实际的参数而不是va_list。 - John Zwinck
@JohnZwinck:的确,谢谢你的纠正。 - Matthieu M.


我认为这种方式更有意义:

void logPrintf(const char *fmt, ...) {
        va_list ap;    // log to logfile
        va_start(ap, fmt);
        logOpen;
        vfprintf(flog, fmt, ap); //logfile
         printf(fmt, ap); //console
        logClose;
        va_end(ap);
    }

-2
2018-02-16 10:11



谢谢托尼,但这也不起作用。这就像指针留在列表的末尾,所以第二次使用会变成垃圾。 - Neddie
是的,这正是发生的事情。 va_lists总是通过引用传递。 - David Given
这个例子(即使有 vprintf代替 printf)根据手册页错误:“如果ap被传递给使用va_arg(ap,type)的函数,那么在返回该函数后,ap的值是未定义的。”在linux x86-32上,它按预期工作,但不是例如在x86-64上。 - Armin Rigo
由于上述原因,此代码是错误的 Armin Rigo 在他们的 评论  - 事实上,两个早先的评论。解决方案需要 va_end(ap); va_start(ap, fmt); 插入之间 vfprintf() 和 printf(),和 printf() 需要被替换 vprintf()。 不要尝试使用此代码编写。 - Jonathan Leffler