Странное поведение float в определении функции. И несоответствие объявления-определения, но это работает, как?

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

#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, возникает следующая ошибка (идея):

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), но язык настолько загадочен, что я не мог ясно понять. Я даже не знаю, правильно ли я прочитал раздел. Поэтому, пожалуйста, объясните это поведение простыми словами.


person Nawaz    schedule 05.06.2011    source источник


Ответы (4)


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

double f();

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

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

double f(void);

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

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

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

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

person AnT    schedule 05.06.2011

C допускает, чтобы объявление функции было пустым. Из C99 6.7.5.3/14:

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

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

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

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

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

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

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

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

person Oliver Charlesworth    schedule 05.06.2011
comment
Значит, я не могу вызвать f(float), передавая double значение? Я думаю, double можно неявно преобразовать в float? Или я что-то упускаю? - person Nawaz; 05.06.2011
comment
@Nawaz: Если компилятор знает, что функция f(float) в точке вызова, то да, вы можете (double будет преобразовано в float, а float будет помещено в стек или в eax или что-то еще). Но если ему неизвестны типы аргументов, он поместит в стек double. Если ваша функция действительно f(float), то она попытается прочитать float из стека (т. е., по сути, повторно интерпретировать). - person Oliver Charlesworth; 05.06.2011

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

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

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

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

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

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

[редактировать]

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

person Nemo    schedule 05.06.2011
comment
Если вам нужно объявить функцию, не принимающую аргументов, используйте f(void). - person detly; 05.06.2011
comment
Компиляторы не были глупыми (ну, они были, но проблема не в этом): язык был глупым. Просто так работал C до стандарта ANSI. Поведение остается для обратной совместимости. Не используйте его. Используйте прототипы функций. - person andrewdski; 05.06.2011

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

person andrewdski    schedule 05.06.2011