Почему void f() не разрешен в C?

Почему C не разрешает функцию со списком аргументов переменной длины, например:

void f(...)
{
    // do something...
}

person Rajendra Uppal    schedule 04.07.2011    source источник
comment
Предварительный стандарт C допускал такие функции.   -  person chux - Reinstate Monica    schedule 03.09.2014


Ответы (6)


Я думаю, что мотивация требования, чтобы функции varargs имели именованный параметр, связана с единообразием va_start. Для простоты реализации va_start принимает имя последнего именованного параметра. При типичном соглашении о вызове varargs и в зависимости от того, в каком направлении хранятся аргументы, va_arg найдет первый vararg по адресу (&parameter_name) + 1 или (first_vararg_type*)(&parameter_name) - 1, плюс или минус некоторое заполнение для обеспечения выравнивания.

Я не думаю, что есть какая-то конкретная причина, по которой язык не мог поддерживать функции с переменным числом аргументов без именованных параметров. Должна быть альтернативная форма va_start для использования в таких функциях, которая должна была бы получать первый vararg непосредственно из указателя стека (или, если быть педантичным, указатель фрейма, который фактически является значением, которое указатель стека имел на вход в функцию, так как код в функции вполне мог переместить sp с момента входа в функцию). В принципе это возможно — любая реализация должна иметь доступ к стеку [*] каким-то образом, на каком-то уровне — но некоторых разработчиков это может раздражать. Как только вы узнаете соглашение о вызовах varargs, вы сможете реализовать макросы va_ без каких-либо других знаний, связанных с реализацией, а для этого потребуется также знать, как напрямую обращаться к аргументам вызова. Я реализовал эти макросы varargs раньше, на уровне эмуляции, и это меня бы раздражало.

Кроме того, не так много практического применения функции varargs без именованных параметров. Для функции varargs нет языковой функции для определения типа и количества переменных аргументов, поэтому вызываемый объект в любом случае должен знать тип первого vararg, чтобы прочитать его. Таким образом, вы также можете сделать его именованным параметром с типом. В printf и друзьях значение первого параметра сообщает функции, какие типы имеют varargs и сколько их существует.

Я предполагаю, что теоретически вызываемый объект мог бы посмотреть на какой-то глобальный объект, чтобы понять, как читать первый аргумент (и есть ли он вообще), но это довольно неприятно. Я, конечно, не стал бы изо всех сил поддерживать это, и добавление новой версии va_start с дополнительным бременем реализации выходит за рамки моего пути.

[*] или, если реализация не использует стек, к тому, что он использует вместо этого для передачи аргументов функции.

person Steve Jessop    schedule 04.07.2011
comment
Unix <varargs.h> (предшественник и вдохновитель стандартного средства) допускал только переменные аргументы. Обоснование C гласит, что возможность иметь аргументы фиксированного типа перед varargs является нововведением процесса стандартизации. Я также отмечу, что C++ позволяет не иметь аргумента фиксированного типа, и что единственное его использование, которое я видел, было в контекстах метапрограммирования шаблонов. - person AProgrammer; 04.07.2011
comment
Я думаю, что ... поэтому вызываемый объект все равно должен знать тип первого vararg, чтобы его прочитать. это ключевое понимание здесь. - person caf; 05.07.2011

Со списком аргументов переменной длины вы должны объявить тип первого аргумента - это синтаксис языка.

void f(int k, ...)
{
    /* do something */
}

будет работать нормально. Затем вам нужно использовать va_list, va_start, va_end и т. д. внутри функции для доступа к отдельным аргументам.

person Aleks G    schedule 04.07.2011
comment
+1 за единственный ответ того, кто действительно прочитал вопрос и объяснил, почему это запрещено. - person ; 04.07.2011
comment
@WTP: строго говоря, ...за то, что это был первый ответ того, кто на самом деле читал [...] и прояснил это - person sehe; 04.07.2011
comment
imo, потому что его синтаксис - это не объяснение, а просто констатация факта. - person ShinTakezou; 04.07.2011
comment
Рассмотрим трилемму Мюнхгаузена. - person Michael Foukarakis; 04.07.2011
comment
@Aleks: Весь ваш ответ является только информацией, это определение использования списка var arg в C, а не ответ на мой вопрос. C++ поддерживает void f(...); без проблем! - person Rajendra Uppal; 07.07.2011

C позволяет использовать аргументы переменной длины, но вам нужно использовать va_list, va_start, va_end и т. д. . для этого. Как вы думаете, как реализованы printf и друзья? Тем не менее, я бы рекомендовал против этого. Обычно вы можете сделать то же самое более аккуратно, используя массив или структуру для параметров.

person Michael Aaron Safyan    schedule 04.07.2011
comment
И va_start нужно имя параметра функции. - person lhf; 04.07.2011

Поэкспериментировав с этим, я сделал эту замечательную реализацию, которую, я думаю, некоторые люди захотят рассмотреть.

template<typename T>
void print(T first, ...)
{
    va_list vl;
    va_start(vl, first);
    T temp = first;
    do
    {
        cout << temp << endl;
    }
    while (temp = va_arg(vl, T));
    va_end(vl);
}

Это гарантирует, что у вас есть минимум одной переменной, но позволяет вам поместить их все в цикл чистым способом.

person Philippe Paré    schedule 02.10.2014

Нет внутренней причины, по которой C не может принять void f(...). Могло бы, но «дизайнеры» этой функции C решили этого не делать.

Я предполагаю, что их мотивы заключаются в том, что разрешение void f(...) потребует больше «скрытого» кода (который можно рассматривать как среду выполнения), чем его запрет: чтобы сделать отличимым случай f() от f(arg ) (и другие), C должен предоставить способ подсчета количества заданных аргументов, и для этого требуется больше сгенерированного кода (и, вероятно, новое ключевое слово или специальная переменная, например, "nargs" для получения количества), и C обычно старается быть как можно более минималистичным.

person ShinTakezou    schedule 04.07.2011

... не допускает аргументов, то есть: для int printf(const char *format, ...); оператор

printf("foobar\n");

является действительным.

Если вы не укажете хотя бы 1 параметр (который следует использовать для проверки дополнительных параметров), функция не сможет «узнать», как она была вызвана.

Все эти утверждения будут справедливы

f();
f(1, 2, 3, 4, 5);
f("foobar\n");
f(qsort);
person maraguida    schedule 04.07.2011