Пример использования varargs в C
Здесь Я нашел пример того, как varargs можно использовать в C.
#include <stdarg.h>
double average(int count, ...)
{
va_list ap;
int j;
double tot = 0;
va_start(ap, count); //Requires the last fixed parameter (to get the address)
for(j=0; j<count; j++)
tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
va_end(ap);
return tot/count;
}
Я могу понять этот пример только в некоторой степени.
-
Мне непонятно, почему мы используем va_start(ap, count);
. Насколько я понимаю, таким образом мы устанавливаем итератор в свой первый элемент. Но почему он не установлен в начале по умолчанию?
-
Мне непонятно, почему нам нужно дать count
в качестве аргумента. Не может ли C автоматически определить количество аргументов?
-
Мне непонятно, почему мы используем va_end(ap)
. Что это изменит? Установил ли он итератор в конце списка? Но он не установлен в конец списка по циклу? Более того, зачем нам это нужно? Мы больше не используем ap
; почему мы хотим его изменить?
Ответы
Ответ 1
Помните, что аргументы передаются в стеке. Функция va_start
содержит "магический" код для инициализации va_list
с правильным указателем стека. Он должен быть передан последним именованным аргументом в объявлении функции или не будет работать.
В этом va_arg
используется этот сохраненный указатель стека и извлекается правильное количество байтов для предоставленного типа, а затем изменяется ap
, поэтому он указывает на следующий аргумент в стеке.
В действительности эти функции (va_start
, va_arg
и va_end
) не являются фактически функциями, а реализованы как макросы препроцессора. Фактическая реализация также зависит от компилятора, поскольку разные компиляторы могут иметь разный компоновщик стека и как он толкает аргументы в стеке.
Ответ 2
va_start инициализирует список переменных аргументов. Вы всегда передаете последний аргумент функции как второй параметр. Это связано с тем, что вам необходимо предоставить информацию о местоположении в стеке, где начинаются переменные аргументы, поскольку аргументы вставляются в стек, а компилятор не может знать, где есть начало списка переменных аргументов (нет дифференцирования).
Что касается va_end, он используется для освобождения ресурсов, выделенных для списка переменных аргументов во время вызова va_start.
Ответ 3
Но почему он по умолчанию не установлен?
Возможно, из-за исторических причин, из-за которых компиляторы недостаточно умен. Возможно, потому, что у вас может быть прототип функции varargs, который на самом деле не заботится о varargs, и настройка varargs оказывается дорогостоящей в этой конкретной системе. Возможно, из-за более сложных операций, в которых вы выполняете va_copy
, или, возможно, вам нужно перезапустить работу с аргументами несколько раз и вызвать va_start
несколько раз.
Короткий вариант: потому что язык говорит об этом.
Во-вторых, мне непонятно, почему нам нужно давать счет в качестве аргумента. Не может ли С++ автоматически определять количество аргументов?
Это не то, что все это count
. Это последний аргумент функции. va_start
нужно выяснить, где находятся varargs. Скорее всего, это связано с историческими причинами старых компиляторов. Я не понимаю, почему сегодня это невозможно реализовать по-другому.
Как вторая часть вашего вопроса: нет, компилятор не знает, сколько аргументов было отправлено этой функции. Это может быть даже не в том же компиляционном блоке или даже в той же программе, и компилятор не знает, как будет вызвана функция. Представьте себе библиотеку с функцией varargs, например printf
. Когда вы компилируете свой libc, компилятор не знает, когда и как программы вызовут printf
. В большинстве ABI (ABI - это соглашения о том, как вызываются функции, как передаются аргументы и т.д.), Нет способа узнать, сколько аргументов получил вызов функции. Это расточительно включать эту информацию в вызов функции, и она почти никогда не нужна. Таким образом, вам нужно иметь способ сообщить функции varargs, сколько аргументов она получила. Доступ к va_arg
за пределами числа аргументов, которые были фактически переданы, - это поведение undefined.
Тогда мне непонятно, почему мы используем va_end (ap). Что он изменил?
В большинстве архитектур va_end
нет ничего важного. Но есть некоторые архитектуры со сложной семантикой передачи аргументов, а va_start
может даже потенциально хранить память malloc, тогда вам понадобится va_end
, чтобы освободить эту память.
Краткая версия здесь также: потому что язык говорит об этом.
Ответ 4
Это макросы. va_start
устанавливает внутренний указатель на адрес первого элемента. va_end
очистка va_list
. Если в коде есть va_start
и нет va_end
- это UB.
Ограничения, которые ISO C помещает во второй параметр в макрос va_start() в заголовке
в этом международном стандарте отличаются. Параметр parmN является идентификатором самого правого параметра
в списке переменных параметров определения функции (тот, который был непосредственно перед...). Если параметр
parmN объявляется с помощью функции, массива или ссылочного типа или с типом, который несовместим с
тип, который возникает при передаче аргумента, для которого нет параметра, поведение undefined.