Понимание разницы между f() и f(void) в C и C++ раз и навсегда

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

Для С++

Объявления void f(); и void f(void); означают одно и то же, функция f не принимает никаких параметров. То же самое для определений.

Для С

Объявление void f(void); означает, что f не принимает никаких параметров.

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

Теперь здесь все становится интереснее.

Дело 1

Декларация:

void f();

Определение:

void f(int a, int b, float c)
{
   //...
}

Случай 2

Декларация:

void f();

Определение:

void f()
{
   //...
}

Вопрос:

Что происходит во время компиляции в случаях 1 и 2, когда мы вызываем f с правильными аргументами, неправильными аргументами и вообще без аргументов? Что происходит во время выполнения?

Дополнительный вопрос:

Если я объявлю f с аргументами, но определю без них, будет ли это иметь значение? Должен ли я иметь возможность обращаться к аргументам из тела функции?


person Community    schedule 10.11.2012    source источник
comment
в C void f(); совпадает с void f(void);. В одном вы неявно говорите об отсутствии аргументов (компилятор делает вывод об этом), а в другом вы явно говорите об отсутствии аргументов. Также будет лучше, если вы попробуете эти примеры самостоятельно, чтобы вам было удобнее использовать компилятор позже.   -  person yeyo    schedule 10.11.2012
comment
@Dietrich (это то, чего я не знал до сих пор, спасибо), но, как указал paxdiablo, это происходит только при объявлении, я имел в виду при определении функции. Хорошо, pastebin.com/MqhDBZg1 по этой ссылке программа, которую кто-то может попытаться понять из этого (как я сделал :))   -  person yeyo    schedule 10.11.2012
comment
AFAIK в C99 void f(); то же самое, что и void f(void); // поправьте меня если я ошибаюсь   -  person martinkunev    schedule 29.04.2014
comment
@paxdiablo void f(); не является прототипом (это объявление, которое не является прототипом).   -  person M.M    schedule 20.08.2014


Ответы (4)


Еще терминология (C, а не C++): прототип функции объявляет типы ее аргументов. В противном случае функция не имеет прототипа.

void f();                      // Declaration, but not a prototype
void f(void);                  // Declaration and prototype
void f(int a, int b, float c); // Declaration and prototype

Объявления, которые не являются прототипами, являются пережитком C до ANSI, со времен K&R C. Единственная причина использовать объявление в старом стиле — поддерживать двоичную совместимость со старым кодом. Например, в Gtk 2 есть объявление функции без прототипа — оно там случайно, но его нельзя удалить, не нарушив двоичный код. Стандартные комментарии C99:

6.11.6 Деклараторы функций

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

Рекомендация: я предлагаю компилировать весь код C в GCC/Clang с -Wstrict-prototypes и -Wmissing-prototypes в дополнение к обычному -Wall -Wextra.

Что случается

void f(); // declaration
void f(int a, int b, float c) { } // ERROR

Объявление не соответствует телу функции! На самом деле это ошибка времени компиляции, потому что у вас не может быть аргумента float в функции без прототипа. Причина, по которой вы не можете использовать float в непрототипной функции, заключается в том, что когда вы вызываете такую ​​функцию, все аргументы продвигаются, используя определенные продвижения по умолчанию. Вот фиксированный пример:

void f();

void g()
{
    char a;
    int b;
    float c;
    f(a, b, c);
}

В этой программе a повышается до int1, а c повышается до double. Таким образом, определение для f() должно быть:

void f(int a, int b, double c)
{
    ...
}

См. C99 6.7.6 параграф 15,

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

Ответ 1

Что происходит во время компиляции в случаях 1 и 2, когда мы вызываем f с правильными аргументами, неправильными аргументами и вообще без аргументов? Что происходит во время выполнения?

Когда вы вызываете f(), параметры продвигаются с использованием продвижения по умолчанию. Если продвигаемые типы соответствуют фактическим типам параметров для f(), то все в порядке. Если они не совпадают, он вероятно скомпилируется, но вы определенно получите неопределенное поведение.

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

Есть два способа получить диагностику во время компиляции. Если у вас есть сложный компилятор с возможностями межмодульного статического анализа, вы, вероятно, получите сообщение об ошибке. Вы также можете получать сообщения для непрототипированных объявлений функций с помощью GCC, используя -Wstrict-prototypes, который я рекомендую включать во всех ваших проектах (за исключением файлов, использующих Gtk 2).

Ответ 2

Если я объявлю f с аргументами, но определю без них, будет ли это иметь значение? Должен ли я иметь возможность обращаться к аргументам из тела функции?

Он не должен компилироваться.

Исключения

На самом деле есть два случая, когда аргументы функции могут не совпадать с определением функции.

  1. Можно передать char * функции, которая ожидает void *, и наоборот.

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

Сноски

1: возможно, что char становится unsigned int, но это очень редко.

person Dietrich Epp    schedule 10.11.2012
comment
То, что происходит, совершенно неправильно - вы НЕ получаете ошибку, не должны. - person paxdiablo; 10.11.2012
comment
@paxdiablo: Но я получаю сообщение об ошибке. GCC: ошибка: конфликтующие типы для «f»; примечание: тип аргумента с продвижением по умолчанию не может соответствовать объявлению пустого списка имен параметров. - person Dietrich Epp; 10.11.2012
comment
@paxdiablo: ошибка возникает в GCC 4.0, 4.2 и 4.7, а также в Clang 3.1. - person Dietrich Epp; 10.11.2012
comment
Вы должны читать ошибку более внимательно. Это не имеет ничего общего с пустым списком как таковым. Это связано с выбранными вами аргументами (поплавок). Он запрещает аргументы с продвижением по умолчанию (float-›double). Вы обнаружите, что если вы измените значение float на int или double, ваша ошибка исчезнет. - person paxdiablo; 10.11.2012
comment
@paxdiablo: извините, я думал, что именно это и имел в виду. Посмотрите на второй пример, который меняет тип на double. - person Dietrich Epp; 10.11.2012
comment
Так лучше, вы даете понять, почему произошла ошибка. Возможно, я неправильно понял ваше первоначальное утверждение. - person paxdiablo; 10.11.2012

Все это действительно спорный вопрос, если вы используете C99 или более позднюю версию (и, если вы не застряли на старой встроенной системе или что-то в этом роде, вам, вероятно, следует использовать что-то более современное) .

Раздел C99/C11 6.11.6 Future language directions, Function declarators гласит:

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

Следовательно, вам следует вообще избегать использования таких вещей, как void f();.

Если он принимает параметры, перечислите их, сформировав правильный прототип. Если нет, используйте void, чтобы окончательно указать, что он не принимает никаких параметров.

person paxdiablo    schedule 10.11.2012

В С++ f() и f(void) одинаковы

В C они другие, и любое количество аргументов может быть передано при вызове функции f(), но ни один аргумент не может быть передан в f(void)

person rs2012    schedule 10.11.2012

В чистом C это приводит к ошибке: error C2084: function 'void __cdecl f(void )' already has a body

void f(void);
void f( );

int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(void) {

}

void f( ) {

}

.

 fvoid.c line(19) : error C2084: function 'void __cdecl f(void )' already has a body

Но в Pure C++ он скомпилируется без ошибок.

Функции перегрузки (только C++, в C нет перегрузки)

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

пример:

#include <iostream>
using namespace std;

void f(int i);
void f(double  f);
void f(char* c);


int main() {
  f(10);
  f(10.10);
  f("ten");

  return 0;
}

void f(int i) {
  cout << " Here is int " << i << endl;
}
void f(double  f) {
  cout << " Here is float " << f << endl;
}

void f(char* c) {
  cout << " Here is char* " << c << endl;
}

вывод:

 Here is int 10
 Here is float 10.1
 Here is char* ten
person Software_Designer    schedule 10.11.2012
comment
Итак... какое это имеет отношение к разнице между void f(); и void f(void);? - person Dietrich Epp; 10.11.2012
comment
void f(void) — это правильный способ сказать «нет параметров» в C. Но void f( ) тоже подойдет - person Software_Designer; 10.11.2012
comment
Этот вопрос в основном касается C, не так ли? C не имеет перегрузок. - person GManNickG; 10.11.2012
comment
Но с таким же успехом подойдет и void f(). Нет, не в C. - person Mat; 10.11.2012
comment
разница между void f(); и void f(void) -> В чистом C это приводит к ошибке: error C2084: function 'void __cdecl f(void)' уже имеет тело. В C++ это просто перегрузка функций. - person Software_Designer; 10.11.2012
comment
Первая программа имеет несколько ошибок компиляции на C++. Вторая программа не имеет никакого отношения к вопросу. - person M.M; 29.02.2016