【问题标题】:Repeated use of a variadic function argument doesn't work重复使用可变参数函数参数不起作用
【发布时间】:2012-03-07 17:32:42
【问题描述】:

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

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

【问题讨论】:

  • 第二次需要使用vprintf,而不是printf。

标签: c++ c variadic variadic-functions


【解决方案1】:

原始代码失败,因为它尝试在需要使用vprintf() 的地方使用printf()。从表面上看像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_start() 之前使用va_end(),两次使用同一个是完全可以的。

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

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

va_start 和 va_copy 宏的每次调用 应通过在同一函数中对 va_end 宏的相应调用来匹配。

由于logVprintf() 函数没有调用va_start 或va_copy 来初始化args1,因此它不能在args1 上合法地调用va_end。另一方面,标准要求它为args2 调用va_end。

logPrintf()函数现在可以用logVprintf()来实现了:

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

这种结构——一个接受va_list的操作函数和一个接受省略号(可变参数)并在转换为va_list后将它们传递给操作函数的覆盖函数——通常是一种很好的工作方式。迟早,您通常会发现需要带有 va_list 参数的版本。

【讨论】:

  • 这是直接解决 OP 问题的正确答案,不建议更改其架构。
【解决方案2】:

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

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

    printf(fmt, args...);
}

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

【讨论】:

  • 以上示例需要 C++11。
  • @DavidGiven: 是的(因此升级你的编译器)。
  • 我认为 vfprintf 需要在这里改为 fprintf -- 你传递的是实际参数而不是 va_list。
【解决方案3】:

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

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

【讨论】:

  • 谢谢托尼,但这也不起作用。这就像指针留在列表的末尾,所以第二次使用会得到垃圾。
  • 是的,这正是正在发生的事情。 va_lists 总是通过引用传递。
  • 这个例子(即使使用vprintf而不是printf)根据手册页是错误的:“如果将ap传递给使用va_arg(ap,type)的函数,那么值ap 在该函数返回后未定义。”在 linux x86-32 上它按预期工作,但不是例如在 x86-64 上。
  • 由于Armin Rigo 在他们的comment 中所述的原因,该代码是错误的——事实上,两个早期的cmets。该解决方案需要在vfprintf()和printf()之间插入va_end(ap); va_start(ap, fmt);,并且printf()需要替换为vprintf()。 不要尝试按原样使用此代码。
猜你喜欢
  • 1970-01-01
  • 2014-10-13
  • 2011-04-01
  • 2015-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-05
相关资源
最近更新 更多