使用va_list和va_arg实现子功能

时间:2019-06-28 18:19:30

标签: c variadic-functions

我有一个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()的调用返回的内容不是NULL。

这是一个更为复杂的功能的抄写,但是除非有错别字,我在设置或使用va_listva_arg()时是否错过了某些东西?

2 个答案:

答案 0 :(得分:5)

除了已被@zwol识别出的问题...

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

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

  

NULL扩展为实现定义的空指针常量...

     

值为0的整数常量表达式,或将其强制转换为类型的表达式   void *,称为空指针常量

甚至没有指定

NULL与指针的大小相同。

va_arg(ap, element_t *)是UB。

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

答案 1 :(得分:4)

这样叫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_start(ap, element)(在调用方中)将设置va_arg以首先返回元素。也许那样行不通?

的确,它不能那样工作。 va_start设置va_arg返回第一个 anonymous 参数。如果没有任何匿名参数,那么您第一次调用va_arg时就结束了,并触发了UB。