Странное поведение float в определении функции.А объявление-несоответствие определению, все же работает, как? - PullRequest
5 голосов
/ 05 июня 2011

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

#include <stdio.h>
double f(); //function declaration
int main(void)  
{ 
   printf("%f\n", f(100.0)); 
}
double f(double param) //function definition
{
   return 5 * param ; 
}

Он компилируется и работает нормально ( ideone ).

Но если я изменю тип параметра в определении с double на float, это даст следующую ошибку ( ideone ):

prog.c: 7: ошибка: конфликтующие типы для 'f'
prog.c: 8: примечание: тип аргумента, который имеет продвижение по умолчанию, не может соответствовать пустому объявлению списка имен параметров
prog.c: 2: ошибка: предыдущее объявление 'f' было здесь

Что не так с float?Почему выдается ошибка с float, но не с double?

Вот список пар объявлений и определений, вместе с которыми работает пара, а какие нет:

  • Работает ( ideone )

    double f();              //declaration
    double f(double param);  //definition
    
  • Не работает ( ideone )

    double f();              //declaration
    double f(float param);   //definition
    
  • Работает ( ideone )

    float f();               //declaration
    float f(double param);   //definition
    
  • Не работает ( ideone )

    float f();               //declaration
    float f(float param);    //definition
    

Так что, как кажется, всякий раз, когда тип параметра равен float, он не работает!


Поэтому у меня два основных вопроса:

  • Почему первый пример работает, даже если в объявлении и определении есть несоответствие?
  • Почему он не работает, когда тип параметра float?

Я пытался понять раздел §6.5.2.2 (C99), но язык настолько загадочный, что я не мог ясно понять.Я даже не знаю, правильно ли я прочитал раздел.Поэтому, пожалуйста, объясните это поведение простыми словами.

Ответы [ 4 ]

6 голосов
/ 05 июня 2011

Ваше предположение, что объявление не соответствует определению, неверно. (Это было бы в случае C ++, но не в C). На языке Си

double f();
Объявление

не полностью объявляет функцию, то есть не вводит прототип . Он только объявляет о том, что функция f существует и ее тип возвращаемого значения double. Он абсолютно ничего не говорит о количестве и типах аргументов f s. Аргументы могут быть абсолютно любыми. В этом смысле объявление в вашем примере действительно соответствует определению (то есть оно не противоречит определению, что хорошо для компилятора C).

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

double f(void);

Это действительно противоречило бы определению. То, что у вас изначально нет.

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

Ваш анализ "пар" декларации и определения не совсем корректен. Это ошибочно. Дело не в декларации и определении. Это действительно определение и способ, которым вы вызываете свою функцию. В исходном случае вы вызываете его с аргументом double, а функция объявляется с параметром double. Так что все совпадает. Но когда вы вызываете его с аргументом double и объявляете его с параметром float, вы получаете несоответствие.

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

4 голосов
/ 05 июня 2011

C позволяет объявлению функции быть пустым.От C99 6.7.5.3/14:

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

Это отличается от списка параметров void, в котором явно указано, что функция не имеет аргументов.Начиная с 6.7.5.3/10:

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

Обратите внимание, что если типы не объявлены, то ваше объявление не является прототипом.От 6.2.1 / 2:

Прототип функции - это объявление функции, которая объявляет типы ее параметров.

Второй вопрос действительно связан с C996.5.2.2/6:

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

Так что в вашем случае float повышается до double всякий раз, когда вызывается функция, и double помещается в стек вызовов (или вeax или что угодно).Но, конечно, если ваша функция Definition принимает float, она будет считывать float из стека вызовов, что приведет к двоичной несовместимости.Компилятор знает это, отсюда и сообщение об ошибке.

3 голосов
/ 05 июня 2011

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

Такие функции при вызове с числом с плавающей точкой неявно принимают его за double. (И интегральные параметры как int.)

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

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

Рад, что вы допустили эту ошибку в 2011 году, а не в 1985 году, потому что компиляторы были довольно глупыми, и это была кошмарная ошибка, чтобы отследить.

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

[править]

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

1 голос
/ 05 июня 2011

В C пустое объявление функции похоже на использование ... в C ++. То есть соответствует любому количеству и типу аргументов. Проблема с использованием float вместо double заключается в том, что float автоматически повышается до double. При вызове f(...) (заимствовать нотацию C ++) он не знает, какой тип ожидается, поэтому его повышают до удвоения. Позже, когда вы переопределите f для принятия аргумента float, это вступит в противоречие с неявным объявлением f как f(double).

...