Странное поведение float в определении функции. И несоответствие объявления-определения, но это работает, как?
Автор вопроса: NawazОтветов: 4
Как работает следующий код, даже если сигнатура функции в объявлении не совпадает с определением? Объявление функции имеет пустой список параметров, но в определении есть один параметр. Почему компилятор не выдает ошибку?
Но если я изменяю тип параметра в определении с double на float, возникает следующая ошибка (идея):
prog.c:7: ошибка: конфликтующие типы для 'f' prog.c:8: примечание: тип аргумента с продвижением по умолчанию не может соответствовать пустому объявлению списка имен параметров prog.c: 2: ошибка: здесь было предыдущее объявление 'f'
Что не так с float? Почему с float выдает ошибку, а с double нет?
Вот список пар объявления и определения, вместе с тем, какая пара работает, а какая нет:
Итак, кажется, что всякий раз, когда тип параметра равен float, он не работает!
Итак, у меня в основном два вопроса:
Почему первый пример работает, хотя в объявлении и определении есть несоответствие?
Почему это не работает, когда тип параметра float?
Я пытался понять раздел §6.5.2.2 (C99), но язык настолько загадочен, что я не мог ясно понять. Я даже не знаю, правильно ли я прочитал раздел. Поэтому, пожалуйста, объясните это поведение простыми словами.
Ваше предположение о том, что объявление не соответствует определению, неверно. (Это было бы так в C++, но не в C). На языке Си
double f();
объявление не полностью объявляет функцию, т. е. не вводит прототип. Он только объявляет о том, что функция f существует и что ее возвращаемый тип - double. Он абсолютно ничего не говорит о количестве и типах аргументов fs. Аргументы могут быть абсолютно любыми. В этом смысле объявление в вашем примере соответствует определению (т. е. не противоречит определению, что достаточно для компилятора C).
Если бы вы действительно хотели объявить функцию, не принимающую аргументов, вам нужно было бы явно указать void в списке параметров.
double f(void);
Это действительно противоречило бы определению. То что у вас изначально нет.
Когда вы вызываете функцию, которая была объявлена с пустым списком параметров (), вы несете ответственность за предоставление надлежащего количества аргументов надлежащего типа. Если вы допустили ошибку, поведение не определено. Это то, о чем компилятор предупреждает вас, когда вы меняете фактический тип параметра на float.
Ваш анализ "пар" объявления и определения не совсем корректен. Это ошибочно. На самом деле речь не идет о декларации и определении. На самом деле речь идет об определении и о том, как вы вызываете свою функцию. В исходном случае вы вызываете ее с аргументом double, а функция объявляется с параметром double. Так что все совпадает. Но когда вы вызываете его с аргументом double и объявляете с параметром float, вы получаете несоответствие.
Также обратите внимание, что когда функция объявляется без прототипа, float аргументов всегда повышаются до double аргументов. По этой причине невозможно передать аргумент float функции, объявленной со списком параметров (). Если вы хотите иметь float аргументов, всегда используйте прототипы (то же самое относится и к char и short аргументам).
Ответ 2 · рейтинг 4 · Oliver Charlesworth
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 из стека вызовов, что приведет к двоичной несовместимости. Компилятор знает об этом, отсюда и сообщение об ошибке.
Ответ 3 · рейтинг 3 · Nemo
В C пустой список параметров в объявлении означает, что функция может быть вызвана с 0 или более аргументами.
Такие функции при вызове с числом с плавающей запятой неявно принимают его как double. (И интегральные параметры как int.)
Поэтому, когда вы вызываете foo(100.0), вы вызываете его с двойным значением. Если вы попытаетесь вызвать его с числом с плавающей запятой, аргумент будет преобразован в двойной во время вызова.
Это не сработает, если вы определите функцию для получения числа с плавающей запятой, потому что способ передачи удвоения и числа с плавающей запятой отличается. Следовательно, компилятор услужливо дает вам ошибку.
Радуйтесь, что вы допустили эту ошибку в 2011, а не в 1985, потому что раньше компиляторы были довольно глупыми, и отследить эту ошибку было кошмарно.
Вывод: очень плохой стиль объявлять функции с пустыми списками параметров в современном C. Объявите функцию правильно, и если на нее будут ссылаться несколько единиц перевода, поместите объявление в заголовочный файл.
[редактировать]
Как указывает detly в комментарии, если вы действительно хотите объявить функцию с нулевыми аргументами, объявите ее принимающей void. (Или переключиться на С++...)
Ответ 4 · рейтинг 1 · andrewdski
В C пустое объявление функции похоже на использование ... в C++. То есть он соответствует любому количеству и типу аргументов. Проблема с использованием float вместо double заключается в том, что float автоматически превращается в double. При вызове f(...) (заимствуя нотацию С++) он не знает, какой тип ожидается, поэтому он повышается до двойного. Позже, когда вы повторно объявите f, чтобы принять аргумент float, это конфликтует с неявным объявлением f как f(double).