Подтвердить что ты не робот

Пример использования 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; почему мы хотим его изменить?

4b9b3361

Ответ 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.