我可以两次使用“ va_list”吗?

时间:2019-03-21 05:38:23

标签: c

我可以按以下方式使用va_list

void myself_printf(char* key, char* value, ...) {
    char real_key[1024];
    char real_value[1024];
    va_list args;
    va_start(args, value);

    // use args twice format key and value
    vsnprintf(real_key, 1024000, key, args);
    vsnprintf(real_value, 1024000, value, args);

    va_end(args);
}

在演示中使用

myself_printf("%d-%s", "%s-%d", 12, "key", "value", 24);
expect: real_key is "12-key", and real_value is "value-24"

2 个答案:

答案 0 :(得分:5)

严格来说,必须使用va_copy,因为vsnprintf会使args无效。

void myself_printf(char* key, char* value, ...) {
    char real_key[1024000];
    char real_value[1024000];
    va_list args, args2;
    va_start(args, value);
    va_copy(args2, args);

    // use args twice format key and value
    vsnprintf(real_key, 1024000, key, args);
    vsnprintf(real_value, 1024000, value, args2);

    va_end(args);
    va_end(args2);
}

这是va_copy设计的目的。

如上所述,这是大量的堆栈空间,尽管它在典型的堆栈大小之内。如果可用,请考虑使用vasprintf

引用

n1548§7.16

  

对象ap可以作为参数传递给另一个函数;如果该函数使用参数ap调用va_arg宏,则调用函数中ap的值不确定...

换句话说,将args传递给vsnprintf后,便无法移植。

在脚注281中对此进行了澄清:

  

当函数vfprintf,vfscanf,vprintf,vscanf,vsnprintf,vsprintf和vsscanf调用va_arg宏时,返回后的arg的值不确定。

虽然va_list是按值传递的,但这并不意味着它实际上是按值传递的,或者va_list本身封装了其所有状态。 C中使用typedef的常见技巧是将它们声明为1元素数组:

typedef int my_type[1];

由于my_type在通过函数传递时会衰减为指针类型,因此它仅出现才能通过值传递。

演示

#include <stdio.h>
#include <stdarg.h>
void func(const char *msg, ...) {
    va_list ap;
    va_start(ap, msg);
    vfprintf(stdout, msg, ap);
    vfprintf(stdout, msg, ap);
    va_end(ap);
}
int main(int argc, char **argv) {
    func("%d + %d = %d\n", 2, 3, 5);
    return 0;
}

在我的计算机上,输出为:

2 + 3 = 5
590862432 + -1635853408 = 1586038440

答案 1 :(得分:3)

作为Dietrich Eppanswer中建议的使用va_copy()的替代方法,您可以简单地两次使用va_start()va_end()

void myself_printf(char *key_fmt, char *value_fmt, ...)
{
    char real_key[1024];
    char real_value[1024];
    va_list args;

    va_start(args, value);
    vsnprintf(real_key, sizeof(real_key), key_fmt, args);
    va_end(args);

    vs_start(args, value);
    vsnprintf(real_value, sizeof(real_key), value_fmt, args);
    va_end(args);

    …do something useful with real_key and real_value…
}

问题的原始版本使用char real_key[1024000];,类似地用于real_value。在Windows上将几乎2 MiB的数据分配给Windows不能可靠地工作(通常只有1 MiB的限制),而在Unix系统上(堆栈大小通常为8 MiB)会占用大量空间。小心点!

如果将va_copy()作为参数传递给函数,则需要使用va_list。例如:

void myself_printf(char* key, char* value, ...)
{
    va_list args;    
    va_start(args, value);
    myself_vprintf(key, value, args);
    va_end(args);
}

void myself_vprintf(char *key_fmt, char *value_fmt, va_list args1)
{
    char real_key[1024];
    char real_value[1024];
    va_list args2;
    va_copy(args2, args1);

    vsnprintf(real_key, sizeof(real_key), key_fmt, args1);
    vsnprintf(real_value, sizeof(real_value), value_fmt, args2);
    va_end(args2);

    …do something useful with real_key and real_value…
}

va_end()的规范说:

  

va_end宏有助于从函数的正常返回,该函数的变量参数列表由va_start宏的扩展引用,或者包含va_copy宏的扩展的函数,它初始化了va_list apva_end宏可以修改ap,使其不再可用(无需通过va_startva_copy宏重新初始化)。如果没有对va_startva_copy宏的相应调用,或者如果在返回之前未调用va_end宏,则行为是不确定的。

请注意,vfprintf()函数的规范包括脚注288,其说明:

  

随着功能vfprintfvfscanfvprintfvscanfvsnprintfvsprintfvsscanf的调用, va_arg宏,返回后的arg的值不确定。

当然,脚注也不是规范性的,但这强烈表明该函数应使用va_arg,因此,如问题所示重复使用arg会导致未定义的行为,除非存在是对va_end和另一个va_start的干预,或对va_copy(及其匹配的va_end的使用)的调用。