我有一个varargs风格的函数,我想把它拆分成一个va_list风格的子函数原始功能:

void container_append(container_t *c, element_t *element, ...) {
  element_t *e;
  va_list ap;

  va_start(ap, element);
  while((e = va_arg(ap, element_t *)) != NULL) {
    container_append_aux(c, e);
  }
  va_end(ap);
}

请注意,调用者必须用NULL终止元素列表,但这不会导致任何问题重构:
void container_append(container_t *c, element_t *element, ...) {
  va_list ap;
  va_start(ap, element);
  container_vappend(c, ap);
  va_end(ap);
}

void container_vappend(container_t *c, va_list ap) {
  element_t *e;
  while ((e = va_arg(ap, element_t *)) != NULL) {
    container_append_aux(c, e);
  }
}

然而,当我这样称呼它时:
container_append(c, NULL);

... 在container_vappend()内部,对va_arg()的调用返回了不为空的内容。
这是一个更复杂的函数的转录,但是除了任何打字错误,我是否在va_listva_arg()的设置或使用中遗漏了什么?

最佳答案

当这样调用container_append

container_append(c, NULL);

命名参数element将为0,并且不会有任何匿名参数在这些条件下,container_append根本不能调用va_arg,否则程序具有未定义的行为。在重构代码之前,它碰巧是意外工作的,但是原始代码和重构版本一样有缺陷。
您可以在循环之前检查element。。。
void
container_append(container_t *c, element_t *element, ...)
{
    if (!element) return;

    container_append_aux(c, element);

    va_list ap;
    va_start(ap, element);
    while ((element = va_arg(ap, element_t *)))
        container_append_aux(c, element);
    va_end(ap);
}

... 或者可以将所有元素参数设为匿名:
void
container_append(container_t *c, ...)
{
    va_list ap;
    va_start(ap, c);

    element_t *e;
    while ((e = va_arg(ap, element_t *)))
        container_append_aux(c, e);

    va_end(ap);
}

后一种结构与您要执行的重构更兼容。
编辑:关于注释中的此查询:
我以为vappend(在调用者中)会设置va_arg以首先返回元素也许这样不行?
事实上,它不是那样工作的。va_start(ap, element)设置va_start以返回第一个匿名参数如果没有任何匿名参数,那么您在第一次调用va_arg并触发UB时就离开了末尾。

关于c - 使用va_list和va_arg实现子功能,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56811604/

10-09 18:20