Пример использования 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;
}

Я могу понять этот пример только до некоторой степени.

  1. Мне не понятно, почему мы используем va_start(ap, count);, Насколько я понимаю, таким образом мы устанавливаем итератор в его первый элемент. Но почему он не установлен в начало по умолчанию?

  2. Мне не ясно, почему мы должны дать count в качестве аргумента. C не может автоматически определить количество аргументов?

  3. Мне не понятно, почему мы используем 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 объявлен с функцией, массивом или ссылочным типом или с типом, который не совместим с типом, который получается при передаче аргумента, для которого нет параметра, поведение не определено.

Другие вопросы по тегам