使用 va_list 和 va_arg 实现子功能

Implementing a sub-function with va_list and va_arg

我有一个可变参数样式的函数,我想拆分成一个 va_list-style 子函数。原函数:

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() 的调用返回了非 NULL 的内容。

这是一个更复杂的函数的转录,但除了任何拼写错误,我是否遗漏了 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重构。


编辑:关于评论中的这个查询:

I thought va_start(ap, element) (in the caller) would set up va_arg to return element first. Maybe it doesn't work that way?

的确,这样不行。 va_start 设置 va_arg 到 return 第一个 anonymous 参数。如果没有任何匿名参数,那么您会在第一次调用 va_arg 时离开最后并触发 UB。

此外发的识别很好 ...

container_append(c, one or more arguments, NULL); 是潜在的未定义行为 (UB)。

container_append() 期望 element_t *elementNULL 可能只是 0.

NULL which expands to an implementation-defined null pointer constant ...

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

NULL 甚至没有指定与指针大小相同。

va_arg(ap, element_t *)就是UB.

为了更安全地拨打电话,请使用container_append(c, args, (element_t *) NULL);