Пример использования 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
больше; почему мы хотим изменить это?
4 ответа
Помните, что аргументы передаются в стеке. va_start
Функция содержит "магический" код для инициализации va_list
с правильным указателем стека. Ему нужно передать последний именованный аргумент в объявлении функции, иначе он не будет работать.
Какие va_arg
делает это использовать этот сохраненный указатель стека, и извлечь правильное количество байтов для предоставленного типа, а затем изменить ap
поэтому он указывает на следующий аргумент в стеке.
На самом деле эти функции (va_start
, va_arg
а также va_end
) на самом деле не являются функциями, а реализованы как макросы препроцессора. Реальная реализация также зависит от компилятора, поскольку разные компиляторы могут иметь разную компоновку стека и то, как он помещает аргументы в стек.
Но почему он не установлен в начало по умолчанию?
Возможно из-за исторических причин, когда компиляторы не были достаточно умны. Возможно, потому что у вас может быть прототип функции varargs, который на самом деле не заботится о varargs, и настройка varargs оказывается дорогостоящей в этой конкретной системе. Может быть из-за более сложных операций, где вы делаете va_copy
или, может быть, вы хотите возобновить работу с аргументами несколько раз и вызвать va_start
многократно.
Краткая версия: потому что языковой стандарт говорит так.
Во-вторых, мне не ясно, почему мы должны считать в качестве аргумента. Разве C++ не может автоматически определить количество аргументов?
Это не то, что все это count
является. Это последний названный аргумент функции. va_start
это нужно, чтобы выяснить, где находятся варяги. Скорее всего, это по историческим причинам на старых компиляторах. Я не понимаю, почему это не могло быть реализовано по-другому сегодня.
В качестве второй части вашего вопроса: нет, компилятор не знает, сколько аргументов было отправлено функции. Он может даже не находиться в одном модуле компиляции или даже в той же программе, и компилятор не знает, как будет вызываться функция. Представьте себе библиотеку с функцией varargs, например printf
, Когда вы компилируете свой libc, компилятор не знает, когда и как программы будут вызывать printf
, В большинстве ABI (ABI - это соглашения о том, как вызываются функции, как передаются аргументы и т. Д.), Нет способа узнать, сколько аргументов получил вызов функции. Бесполезно включать эту информацию в вызов функции, и она почти никогда не нужна. Таким образом, вы должны иметь способ сообщить функции varargs, сколько аргументов она получила. Доступ к va_arg
кроме количества фактически переданных аргументов неопределенное поведение.
Тогда мне не понятно, почему мы используем va_end(ap). Что это меняет?
На большинстве архитектур va_end
не делает ничего релевантного. Но есть некоторые архитектуры со сложной семантикой передачи аргументов и va_start
может даже потенциально маллок памяти, то вам нужно va_end
чтобы освободить эту память.
Краткая версия здесь также: потому что языковой стандарт говорит так.
va_start инициализирует список переменных аргументов. Вы всегда передаете последний именованный аргумент функции в качестве второго параметра. Это потому, что вам нужно предоставить информацию о расположении в стеке, где начинаются переменные аргументы, так как аргументы помещаются в стек, и компилятор не может знать, где находится начало списка аргументов переменной (без дифференциации).
Что касается va_end, он используется для освобождения ресурсов, выделенных для списка переменных аргументов во время вызова va_start.
Это C макросов. va_start
устанавливает внутренний указатель на адрес первого элемента. va_end
уборка va_list
, Если там есть va_start
в коде и нет va_end
- это UB.
Ограничения, которые ISO C накладывает на второй параметр для макроса va_start() в заголовке, отличаются в этом международном стандарте. Параметр parmN является идентификатором самого правого параметра в списке переменных параметров определения функции (тот, который находится перед...). Если параметр parmN объявлен с функцией, массивом или ссылочным типом или с типом, который не совместим с типом, который получается при передаче аргумента, для которого нет параметра, поведение не определено.