Реализация подфункции с va_list и va_arg - PullRequest
1 голос
/ 28 июня 2019

У меня есть функция в стиле 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, но это не должно вызывать каких-либо проблем. Refactored:

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_list и va_arg()?

Ответы [ 2 ]

5 голосов
/ 28 июня 2019

В дополнение к выданной идентифицированной скважине по @ zwol ...

container_append(c, one or more arguments, NULL); является потенциальным неопределенным поведением (UB).

container_append() ожидает, что element_t *element и NULL могут быть просто 0.

NULL, который расширяется до определенной в реализации постоянной нулевого указателя ...

Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу void *, называется константой нулевого указателя .

NULL не указан даже для того же размера, что и указатель.

va_arg(ap, element_t *) - это UB.

Чтобы сделать звонок безопаснее, используйте container_append(c, args, (element_t *) NULL);

4 голосов
/ 28 июня 2019

Когда 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 для возврата первого из анонимных аргументов. Если не было никаких анонимных аргументов, то вы выходите из конца при первом вызове va_arg и запускаете UB.

...