我有一个关于重新启动可变参数列表(va_list
)的问题。
基本上我想做这样的事情:
void someFunc(char* fmt, ...) {
va_list ap;
va_start(fmt, ap);
otherFuncA(fmt, ap);
// restart ap
otherFuncB(fmt, ap);
// restart ap
...
va_end(ap);
return;
}
现在我的问题是:如何重启ap
?
请注意,此问题与C ++无关,但与C。
有关到目前为止,我找到了两种可能的解决方案,但我想知道哪一个是正确的"或者"最佳实践"。
解决方案1:多个va_start()
使用GCC7,我可以替换行
// restart ap
在上面的例子中
va_end(ap);
va_start(fmt, ap);
将ap
重置为第一个参数。
但是,我不确定这是否真的是有效的代码,或者我很幸运,一些未定义的行为没有破坏结果。
解决方案2:va_copy()
另一个适用于GCC7的解决方案是使用ap
初始化va_copy()
的多个副本,如
void someFunc(char* fmt, ...) {
va_list ap1, ap2;
va_start(fmt, ap1);
va_copy(ap2, ap1);
otherFuncA(fmt, ap1);
otherFuncB(fmt, ap2);
va_end(ap1);
va_end(ap2);
return;
}
这是有效的代码(imo),但由于现在有多个va_list
个实例需要复制,因此效率远低于第一个解决方案。
那么哪种解决方案最好?它是我上面提到的两个中的一个,还是完全不同的东西?
答案 0 :(得分:4)
va_copy
方式有效:这正是va_copy
的用途。你说它的效率远远低于第一个解决方案。我真的不同意。首先它是一个实现细节,但是可变参数列表通常在C中实现为参数堆栈中的指针,指向由va_arg
检索的下一个参数。所以va_copy
不会复制参数列表,而只是一个指针。
但是按va_arg
重新启动列表也是有效的。 C11的n1570草案在7.16.1.3中说va_end宏(强调我的):
...... va_end宏可以修改ap以使其不再可用(不重新初始化 通过va_start 或va_copy宏)。
我的理解是,在第一个va_arg
之后用新的va_end
重新初始化可变参数列表的处理是合法的。
两种方式之间的区别在于va_copy
允许同一列表的并发视图,而va_start
的重新初始化仅允许顺序视图(首先在打开第二个视图之前关闭)。
我的观点是选择的标准不应该是性能,因为va_copy
的开销在体面的实现中应该是可以忽略的,但是你真正的要求:如果你想在列表中一次只有一个视图,坚持va_arg
重新初始化,如果并发列表允许更简单的处理,请在va_copy
的帮助下使用它。
答案 1 :(得分:0)
谢谢大家的有益评论!
我尝试了几种方法,我想我终于找到了两个很好的解决方案。
第一个是解决方案1,正如我在原始问题中所描述的那样(感谢rici)。
第二个可以应用于更复杂的应用程序(因为在我的情况下需要它)。让我们从代码开始:
void valistFunc(char* fmt, va_list ap) {
va_list apcpy;
for (/*all consecutive calls*/) {
va_copy(apcpy, ap);
otherFuncX(fmt, apcpy); // a different function for each iteration
va_end(apcpy);
}
return;
}
void variadicFunc(char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
valistFunc(fmt, ap);
va_end(ap);
return;
}
首先,如果您有多个方法,例如variadicFunc
,但是您希望将某些逻辑保留在一个位置(valistFunc
),则需要两个函数。
但是,您不能将...
作为参数传递给后续函数,因此您需要设置相应的va_list
对象。
然后,GCC抱怨(警告)如果您尝试在不使用va_start
作为参数的函数中使用...
,为什么您不能(或不应该)在{{va_start
中使用valistFunc
1}}(注意:我不知道为什么 GCC抱怨,我只是假设这种行为背后有一些原因)。
相反,您需要实例化一个额外的副本apcpy
,您可以通过va_copy
和va_end
随时重新定位。
我希望这可以帮助别人;)