повторное использование аргументов - PullRequest
0 голосов
/ 07 мая 2018

У меня вопрос по поводу перезапуска списков аргументов с переменным числом аргументов (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 голосов
/ 09 мая 2018

Спасибо всем за полезные комментарии!

Я перепробовал еще несколько подходов и, думаю, наконец нашел два хороших решения.

Первый - это просто решение 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 (примечание: я не не знаю почему GCC жалуется, я просто предполагаю, что за этим поведением есть какая-то причина). Вместо этого вам нужно создать единственную дополнительную копию apcpy, которую вы можете повторно назначать столько раз, сколько захотите, с помощью va_copy и va_end.

Я надеюсь, что это может помочь кому-то еще;)

0 голосов
/ 07 мая 2018

Способ va_copy действителен: именно для этого и был создан va_copy. Вы говорите, что это гораздо менее эффективно, чем первое решение . Я не очень согласен. Во-первых, это подробности реализации, но список переменных аргументов обычно реализуется в C как указатель в стеке параметров, указывающий на следующий аргумент, который должен быть получен с помощью va_arg. Таким образом, va_copy не дублирует список аргументов, а является просто указателем.

Но перезапуск списка на va_arg также допустим. Проект n1570 для C11 говорит в 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.

...