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

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

Для C ++

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

Для C

декларация 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 с аргументами, но определите это без них, это будет иметь значение? Должен ли я иметь возможность обращаться к аргументам из тела функции?

40

Решение

Больше терминологии (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

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

6.11.6 Объявление функций

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

Рекомендация: Я предлагаю компилировать весь код 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, но это очень редко.

51

Другие решения

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

Секция C99 / C11 6.11.6 Future language directions, Function declarators состояния:

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

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

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

6

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

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

3

В чистом 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
-1
По вопросам рекламы [email protected]