Лучше ли использовать аргументы C void «void foo(void)» или нет «void foo()»?[дубликат]

StackOverflow https://stackoverflow.com/questions/693788

  •  22-08-2019
  •  | 
  •  

Вопрос

На этот вопрос уже есть ответ здесь:

Что лучше: void foo() или void foo(void)?С void это выглядит некрасиво и непоследовательно, но мне сказали, что это хорошо.Это правда?

Редактировать:Я знаю, что некоторые старые компиляторы делают странные вещи, но если я использую только GCC, void foo() Хорошо?Воля foo(bar); тогда быть принятым?

Это было полезно?

Решение

void foo(void);

Это правильный способ сказать «без параметров» в C, и это также работает в C++.

Но:

void foo();

Означает разные вещи в C и C++!В C это означает «может принимать любое количество параметров неизвестного типа», а в C++ это означает то же самое, что и foo(void).

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

Другие советы

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

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

И это со списком типов параметров:

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

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

Списки идентификаторов

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

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

Список типов параметров

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

Второй способ объявления функции имеет множество преимуществ.Во-первых, конечно, проверяется количество и типы параметров.Другое отличие состоит в том, что, поскольку компилятор знает типы параметров, он может применять неявные преобразования аргументов к типам параметров.Если список типов параметров отсутствует, это невозможно сделать, и аргументы преобразуются в расширенные типы (это называется повышением аргумента по умолчанию). char станет int, например, в то время как float станет double.

Составной тип для функций

Кстати, если файл содержит и список пропущенных идентификаторов, и список типов параметров, то список типов параметров «выигрывает».Тип функции в конце содержит прототип:

void f();
void f(int a) {
    printf("%d", a);
}

// f has now a prototype. 

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

void f(a) 
  int a;
{ 
    printf("%d", a);
}

void f(int);

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

void foo(void) лучше, потому что там явно сказано:никакие параметры не разрешены.

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

Котировки C99

Целью этого ответа является цитирование и объяснение соответствующих частей C99 N1256 стандартный проект.

Определение декларатора

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

Из грамматики языка мы обнаруживаем, что следующие символы подчеркивания являются деклараторами:

int f(int x, int y);
    ^^^^^^^^^^^^^^^

int f(int x, int y) { return x + y; }
    ^^^^^^^^^^^^^^^

int f();
    ^^^

int f(x, y) int x; int y; { return x + y; }
    ^^^^^^^

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

Существует 2 типа деклараторов:

  • список типов параметров
  • список идентификаторов

Список типов параметров

Декларации выглядят так:

int f(int x, int y);

Определения выглядят так:

int f(int x, int y) { return x + y; }

Он называется списком типов параметров, потому что мы должны указать тип каждого параметра.

Список идентификаторов

Определения выглядят так:

int f(x, y)
    int x;
    int y;
{ return x + y; }

Декларации выглядят так:

int g();

Мы не можем объявить функцию с непустым списком идентификаторов:

int g(x, y);

потому что 6.7.5.3 «Деклараторы функций (включая прототипы)» говорит:

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

Он называется списком идентификаторов, потому что мы предоставляем только идентификаторы. x и y на f(x, y), типы идут после.

Это старый метод, и его больше не следует использовать. 6.11.6 Объявители функций говорит:

1 Использование деклараторов функций с пустыми круглыми скобками (а не деклараторов типов параметров в формате прототипа) является устаревшей функцией.

и Введение объясняет, что такое устаревшая функция:

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

f() против f(void) для объявлений

Когда вы пишете просто:

void f();

это обязательно объявление списка идентификаторов, потому что 6.7.5 «Деклараторы» говорит определяет грамматику как:

direct-declarator:
    [...]
    direct-declarator ( parameter-type-list )
    direct-declarator ( identifier-list_opt )

поэтому только версия списка идентификаторов может быть пустой, поскольку она необязательна (_opt).

direct-declarator единственный узел грамматики, определяющий круглые скобки (...) часть декларатора.

Так как же нам устранить неоднозначность и использовать лучший список типов параметров без параметров? 6.7.5.3 Деклараторы функций (включая прототипы) говорит:

10 Особый случай безымянного параметра типа void как единственного элемента в списке указывает, что функция не имеет параметров.

Так:

void f(void);

это путь.

Это явно разрешенный магический синтаксис, поскольку мы не можем использовать void введите аргумент любым другим способом:

void f(void v);
void f(int i, void);
void f(void, int);

Что может произойти, если я использую объявление f()?

Возможно, код скомпилируется нормально: 6.7.5.3 Деклараторы функций (включая прототипы):

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

Таким образом, вам может сойти с рук:

void f();
void f(int x) {}

В других случаях UB может подкрасться (и если вам повезет, компилятор сообщит вам об этом), и вам будет сложно понять, почему:

void f();
void f(float x) {}

Видеть: Почему пустое объявление работает для определений с аргументами типа int, но не для аргументов с плавающей запятой?

f() и f(void) для определений

f() {}

против

f(void) {}

похожи, но не идентичны.

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

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

что похоже на описание f(void).

Но все еще... Кажется, что:

int f() { return 0; }
int main(void) { f(1); }

соответствует неопределенному поведению, в то время как:

int f(void) { return 0; }
int main(void) { f(1); }

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

TODO точно понимает, почему.Имеет отношение к тому, является ли прототип прототипом или нет.Определите прототип.

Помимо синтаксических различий, многие люди также предпочитают использовать void function(void) из практических соображений:

Если вы используете функцию поиска и хотите найти реализацию этой функции, вы можете выполнить поиск function(void), и он вернет как прототип, так и реализацию.

Если вы пропустили второй void, вам придется искать function() и, следовательно, также найдет все вызовы функций, что затрудняет поиск фактической реализации.

В С++ есть нет разница в main() и main(void).

Но в С, main() будет вызван с любой количество параметров.

Пример:

main ( ){
    main(10,"abc",12.28);
    //Works fine !
    //It won't give the error. The code will compile successfully.
    //(May cause Segmentation fault when run)
}

main(void) будет вызван без любые параметры.Если мы попытаемся пройти, это приведет к ошибке компилятора.

Пример:

main (void) {
     main(10,"abc",12.13);
     //This throws "error: too many arguments to function ‘main’ "
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top