我有以下代码:
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_start
和 va_end
的文档,他们所说的只是在调用函数返回之前应该为每个 va_end
调用 va_start
。
我不确定的是调用的顺序是否重要。特别是
va_end(va[0]);
va_end(va[1]);
在本质上与
相同va_end(va[1]);
va_end(va[0]);
在上面的示例代码中?
C99标准中唯一的相关要求是:
7.15.1 变量参数列表访问宏
1 [...]
和va_start
宏的每次调用都应与同一函数中va_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>
标头。你不应该担心他们。
只需为每个 va_start 调用一次 va_end 即可,但您需要使用 va_arg 来获取各个参数。这是一个示例:http://www.cplusplus.com/reference/cstdarg/va_start/
另外,我认为顺序并不重要。
在 some [旧] 实现中,
va_start
扩展为左大括号 {
,后跟一些声明,并且 va_end
扩展为右大括号 }
,前面可能有一些“最终确定”。从道德上讲,他们应该匹配。在实践中,通常但并非总是,顺序并不重要(但原则上它确实很重要)。
在最近的 GCC 上,这些
va_start
和 va_end
宏扩展为 __builtin_va_start
和 __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
的顺序可能并不那么重要。
va_start
和
va_end
是“嵌套”当然,您需要调用
va_arg
来真正检索可变参数(我希望您的
process
正在这样做)。 stdarg(3)很好地解释了这一点(对于 C 代码): 每次调用
对应va_start()
必须与
对应的相匹配 在同一函数中调用va_end()
。 注意
这个词(重点是我的)。我相信这意味着 va_start
和
va_end
确实是 嵌套(至少在原则上)。