Строка формата является обязательной, так как работа макросов с переменными аргументами C зависит от по меньшей мере одного присутствующего аргумента и использования его для поиска других.
В частности, чтобы прочитать другие аргументы переменной, вы используете va_start
(затем va_arg
несколько раз, один раз для каждого аргумента переменной, который вы хотите прочитать). Когда вы вызываете va_start
, вам необходимо передать ему строку формата (или, в более общем плане, последний параметр, не изменяющийся для функции).
Например, это действует как printf
, но печатает на обеих stdout
и другой файл по вашему выбору:
void tee(FILE *f, char const *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
}
Это использует vprintf
и vfprintf
, поэтому он не (непосредственно) использовать va_arg
себя, только va_start
и va_end
, но этого достаточно, чтобы показать, как fmt
участвует в использовании va_start
.
В свое время это фактически не требовалось. Когда C был блестящим и новым, у вас могла бы быть функция, эквивалентная функции: int f(...);
.
Во время первой попытки стандартизации C, однако, это было устранено в пользу макросов, отмеченных выше (va_start
, va_arg
, va_end
), которые требуют, по меньшей мере, один параметр с именем.Старшие макросы устанавливали ряд требований в вызывающем соглашении:
- Параметры всегда передаются одинаково, независимо от типа или номера.
- Всегда можно найти первый аргумент, который был передан.
С обычным соглашением о вызове C (все аргументы передаются в стеке, аргументы перемещаются справа налево), это было правдой. Вы в основном просто смотрели на вершину стека, двигались назад по обратному адресу, и был первый аргумент.
С другими соглашениями о вызовах все было не так просто. Например, просто нажатие аргументов слева направо означает, что первый аргумент (строка формата, в случае printf
) похож на произвольное расстояние вниз по стеку с произвольным количеством других аргументов после него.
Способ, с которым они столкнулись, заключался в том, чтобы передать непосредственно предыдущий (названный) аргумент va_start
(и va_start - это макрос, который обычно использует адрес этого аргумента). Если вы нажмете справа налево, это даст вам адрес, независимо от расстояния, необходимого в стеке, тогда va_arg
может вернуться обратно до стек, чтобы получить другие переменные.
Это, очевидно, рассматривалось как приемлемый компромисс, тем более что функции, которые принимают переменные аргументы, почти всегда принимают по крайней мере один именованный параметр.
Потому что в противном случае звонок был бы не-оператором? – Amit
Вопрос меня озадачивает; как в противном случае будет задан желаемый формат функции? Вы предпочитаете перегрузки для форматированного вывода каждого элементарного типа данных? – Codor
Потому что нет смысла вызывать функцию, если нет переданного ей параметра. –