多个va_end调用的顺序是否重要?

时间:2014-09-03 19:13:41

标签: c++ c variadic-functions

我有以下代码:

va_list va[2];
va_start(va[0], fmt);
va_start(va[1], fmt);
process(fmt, va);
va_end(va[0]);
va_end(va[1]);

我已在各个网站上查看有关va_startva_end的文档,他们所说的只是va_end应该为每个va_start调用{}调用函数返回。

我不确定的是,通话顺序是否重要。特别是

va_end(va[0]);
va_end(va[1]);

完全相同
va_end(va[1]);
va_end(va[0]);

在上面的示例代码中?

3 个答案:

答案 0 :(得分:4)

C99标准中唯一相关的要求是:

  

7.15.1变量参数列表访问宏

     

1 [...] va_startva_copy宏的每次调用都应与相同函数中va_end宏的相应调用相匹配。

不要求多个va_end调用的顺序与va_start的调用相匹配,或者与va_start的调用相反,因此需要实现接受任一顺序

你甚至可以使用像

这样糟糕的混乱
void f(int a, ...) {
  va_list ap;
  goto b;
a:
  va_end(ap);
  return;
b:
  va_start(ap, a);
  goto a;
}

这符合标准的所有要求,因此实现必须接受它。因此,即使是va_end扩展为具有不匹配大括号的内容的技巧也是不允许的。

在实践中,我甚至都不知道va_end当前有任何必要的效果。我能够找到的所有实现,最多将值(或第一个子值,取决于类型)设置为零,这将进一步使用va_arg失败,但不会导致如果从代码中省略va_end,则会出现问题。大多数人甚至不这样做。现在,我实际上不会从代码中删除它,因为实现(当前或未来)实际上可以在其va_end中执行某些操作的合理原因,但您可以假设当前和将来的实现至少会尝试以符合标准要求的方式实施它。

使用#define va_end(ap) }的历史实现就是:历史。他们没有在<stdarg.h>中提供该宏,他们甚至没有<stdarg.h>标头。你不应该担心它们。

答案 1 :(得分:2)

只需为每个va_start调用一次va_end,但是您需要使用va_arg来获取各个参数。这是一个例子:http://www.cplusplus.com/reference/cstdarg/va_start/

另外,我认为订单不重要。

答案 2 :(得分:1)

部分 [旧]实现中,va_start扩展为左括号{,后跟一些声明,va_end扩展为右括号{{ 1}}之前可能会有一些“终结”。在道德上,他们应该匹配。在实践中,通常但并非总是如此,顺序并不重要(但原则上它确实很重要)。

在最近的GCC上,这些}va_start宏扩展为va_end&amp;的调用。 __builtin_va_start因此编译器可能会关心(可能在将来某个版本中)这些正确嵌套。见this。所以“好”的顺序应该是:

__builtin_va_end

在实践中,va_list va[2]; va_start(va[0], fmt); va_start(va[1], fmt); process(fmt, va); va_end(va[1]); va_end(va[0]); 的顺序可能并不重要。

缩进是我的强调va_end&amp; va_start正在“嵌套”

当然,你需要调用va_end来真正检索可变参数(我希望你的va_arg正在这样做)。 stdarg(3)解释得很好(对于C代码):

  

process的每次调用都必须与对应的匹配          在同一个函数中调用va_start()

注意对应的字(强调是我的)。我认为这意味着va_end()va_start实际上是嵌套(至少在原则上)。