C: Неопределенное количество параметров - void foo()

Я читал здесь, что в C void foo() означает, a function foo taking an unspecified number of arguments of unspecified type.

Может ли кто-нибудь дать мне или указать мне пример, когда функция C принимает неопределенное количество аргументов? К чему это можно применить в C? Я не мог найти ничего в Интернете.

Благодарю!

Ответы

Ответ 1

Это объявление функции старого стиля.

Это выражение:

void foo();

объявляет, что foo - это функция, возвращающая void которая принимает неопределенное, но фиксированное число и тип аргументов. Это не означает, что вызовы с произвольными аргументами действительны; это означает, что компилятор не может диагностировать неправильные вызовы с неправильным числом или типом аргументов.

Где-то, возможно, в другой единицы перевода (исходный файл), должно быть определение функции, возможно:

void foo(x, y)
long x;
double *y;
{
    /* ... */
}

Это означает, что любой вызов foo, который не передает два аргумента типа long и double* является недопустимым и имеет неопределенное поведение.

До стандарта ANSI C 1989 года это был единственный вид объявления функций и определения, доступных на этом языке, и бремя написания правильных вызовов функций было полностью на программиста. ANSI C добавили прототипы, объявления функций, которые задают типы параметров функции, которые позволяют проводить проверку вызовов функций во время компиляции. (Эта функция была заимствована с раннего C++.) Современный эквивалент вышесказанного:

void foo(long x, double *y);

/* ... */

void foo(long x, double *y) {
    /* ... */
}

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

Нет оснований для использования деклараций и описаний функций старого стиля в современном коде C. (Я видел аргументы в пользу их использования в некоторых случаях, но считаю их неубедительными).

C также поддерживает переменные функции, такие как printf, которые принимают произвольное количество аргументов, но это отдельная функция. Вариадическая функция должна быть объявлена прототипом, который включает в себя трейлинг ,... (Вызов вариационной функции без видимого прототипа не является незаконным, но имеет неопределенное поведение.) Сама функция использует макросы, определенные в <stdarg.h> для обработки ее параметров. Как и в объявлениях стиля старого стиля, проверка аргументов ,... (хотя некоторые компиляторы могут проверять некоторые вызовы, не существует, например gcc предупреждает, что аргументы в вызове printf несовместимы со строкой формата).

Ответ 2

Это буквально означает, что вы не говорите компилятору, какие аргументы выполняет функция, это означает, что он не защитит вас от вызова его любым произвольным набором аргументов. Вы должны сказать в определении точно, какие аргументы фактически принимаются для функции, которая будет реализована.

Например, вы могли бы использовать это, если бы вы генерировали заголовочный файл для описания внешней функции во внешнем коде, однако вы не знали, что такое подпись функции на самом деле, она все равно будет вызываться с помощью вашего заголовка, но если вы укажете неправильные аргументы в результатах вызова не определены.

Ответ 3

Стандарт вызова функции C позволяет вызывать функцию с нулевым или большим количеством аргументов, а число аргументов может соответствовать или не соответствовать интерфейсу функции.

Способ, которым это работает, заключается в том, что до вызова вызывающего абонента корректировать стек после вызова функции функции, а не вызываемой функции, корректирующей стек, в отличие от других стандартов, таких как Pascal, которые требуют, чтобы вызываемая функция правильно управляла настройкой стека.

Поскольку вызывающий абонент знает, какие аргументы и их типы были перенесены в стек перед вызовом вызываемой функции, а вызываемая функция - нет, то до вызова вызывающего абонента очистить отложенные аргументы из стека после возвращения вызываемой функции.

С обновленными стандартами C описание интерфейса функции вызова стало более сложным, чтобы позволить компилятору обнаруживать и сообщать о проблемах интерфейса, которые исходный стандарт K & R C позволил избежать незамеченным компилятором.

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

Таким образом, для некоторых функций ввода/вывода стандартной библиотеки C вы увидите следующее:

 int sprintf (char *buffer, char *format, ...);

Это указывает на то, что функция sprintf требует, чтобы первым аргументом был указатель char на буфер, второй аргумент был указателем символа на строку формата, и могут быть другие дополнительные аргументы. В этом случае любые дополнительные аргументы должны быть вставлены для спецификаторов формата печати в строке формата. Если строка формата - это просто текстовая строка без формата (например, например,% d для целого), других аргументов не будет.

Новые стандарты C задают набор функций/макросов для использования с переменными списками аргументов, функциями varg. С помощью этих функций/макросов вызываемая функция может проходить через переменную часть списка аргументов и обрабатывать аргументы. Эти функции выглядят примерно так:

int jFunc (int jj, char *form, ...)
{
   va_list myArgs;
   int     argOne;

   va_start (myArgs, form);
   argOne = va_arg (myArgs, int);
   va_end (myArgs);

   return 0;
}

Проблема, которую мы имеем с переменными списками аргументов, состоит в том, что C не имеет способа передать аргумент переменной или даже сколько аргументов. Поэтому разработчик функции должен обеспечить механизм. В случае функций ввода/вывода стандартной библиотеки C это выполняется с форматом, который указывает количество аргументов, следующих за строкой формата, путем указания спецификаторов формата для каждого аргумента. И так как нет ничего, что бы проверяет согласованность, вы можете получить строку формата, которая указывает больше или меньше фактических аргументов, приводящих к выходу мусора или меньшему объему вывода, чем ожидалось.

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

Спецификации нового функционального интерфейса предназначены для снижения вероятности неправильной работы функции. Поэтому новые стандарты рекомендуют использовать декларацию интерфейса функций, чтобы компилятор мог помочь вам, обнаружив проблемы интерфейса и неправильное использование переменной в вызове функции.

Однако, если вы хотите быть рискованным, вам не нужно использовать эту систему безопасности, поэтому, если вам нравится, вы можете просто определить функцию с пустым списком аргументов и крылом.

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

Ответ 4

Объявление void foo(); не обязательно то же самое, что и функция, которая принимает переменное количество аргументов.

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

Определение функции (где тело функции предоставляется) может принимать фиксированное количество аргументов и даже иметь прототип.

Когда такое объявление функции используется, программист делает вызов foo() чтобы получить аргументы, которые foo() ожидает (и их типы) правильно - у компилятора нет информации о списке аргументов foo(), Он также ограничивает тип параметров, которые может использовать определение функции, поскольку, когда функция вызывается без прототипа, поэтому аргументы аргументов по умолчанию будут применены к аргументам вызова функции. Таким образом, функция, объявленная без прототипа, может только ожидать получения аргументов.

Для получения дополнительной информации см. Следующие стандартные разделы C99:

6.7.5.3/14 "Деклараторы функций (включая прототипы)

...

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

...

6.5.2.2 Функциональные вызовы

...

Если выражение, обозначающее вызываемую функцию, имеет тип, который не содержит прототип, то для каждого аргумента выполняются целые рекламные акции, а аргументы с типом float повышаются до двух. Они называются рекламными акциями по умолчанию. Если количество аргументов не равно числу параметров, поведение не определено. Если функция определена с типом, который включает прототип, и либо прототип заканчивается на эллипсис (,...), либо типы аргументов после продвижения по службе не совместимы с типами параметров, поведение не определено. Если функция определена с типом, который не включает прототип, а типы аргументов после продвижения по службе не совместимы с типами параметров после продвижения по службе, поведение не определено, за исключением следующих случаев:

  • один продвинутый тип - это целочисленный тип со знаком, другой продвинутый тип - это соответствующий целочисленный тип без знака, и значение представляется в обоих типах;
  • оба типа являются указателями на квалифицированные или неквалифицированные версии типа символа или void.

Ответ 5

Я знаю, что это немного поздно, но поскольку у foo есть неопределенное количество аргументов неопределенного типа, вы можете привести foo к любому типу функции void, например, по вашему желанию.

void (*ptr)(int) = foo; законно

а также

void (*ptr)(float, int*) = foo; законно

в некоторых случаях это может быть полезно (но, возможно, не очень хорошая практика?), например, передача функции без параметров в pthread_create, и она все равно будет работать