重用可变参数

时间:2018-05-07 14:04:04

标签: c variadic

我有一个关于重新启动可变参数列表(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个实例需要复制,因此效率远低于第一个解决方案。

那么哪种解决方案最好?它是我上面提到的两个中的一个,还是完全不同的东西?

2 个答案:

答案 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_copyva_end随时重新定位。

我希望这可以帮助别人;)