Подтвердить что ты не робот

Объявление функции по сравнению с определением C

У меня есть простой код, где мои функции объявлены перед основной функцией:

int function1();
int function2();

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }

И после моей основной функции у меня есть определения функций:

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

int function1(int x, float y);
int function2(int x, float y);

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }
4b9b3361

Ответ 1

Да, они разные.

В первом примере вы просто говорите компилятору о имени и возвращаемом типе функции и ничего из ожидаемых аргументов.

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

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

Также обратите внимание, что int function() в C - это функция, которая может принимать любые аргументы, а не функцию, принимающую аргументы no. Для этого вам нужен явный void, i.e int function(void). В основном это срабатывает при приближении к C от C++.

См. также: Почему функция, не имеющая параметров (по сравнению с фактическим определением функции), компилируется?

Чтобы продемонстрировать, почему первая, устаревшая форма плоха в современном C, следующая программа компилируется без предупреждения с помощью gcc -Wall -ansi -pedantic или gcc -Wall -std=c11.

#include<stdio.h>
int foo();

int main(int argc, char**argv)
{
  printf("%d\n", foo(100));
  printf("%d\n", foo(100,"bar"));
  printf("%d\n", foo(100,'a', NULL));
  return 0;
}

int foo(int x, int y)
{
  return 10;
}

UPDATE: M & M обратил мое внимание на то, что в моем примере для функций используются int not float. Я думаю, мы все можем согласиться с тем, что объявление int function1() - это плохая форма, но мое утверждение о том, что это объявление принимает любые аргументы, не совсем корректно. См. Ответ Влада в соответствующем разделе спецификации, объясняющем, почему это так.

Ответ 2

Да, они разные; второй правильный, первый из них как целое неверен. Он настолько силен, что GCC 5.2.1 отказывается компилировать его вообще. То, что он работает для вас, - это всего лишь fluke:

/* this coupled with */
int function1();

int main() {
    /* this */
    function1(x, y);
}

/* and this one leads to undefined behaviour */
int function1(int x, float y) {
    /* ... */
}

В приведенном выше коде объявление int function1(); не указывает типы аргументов (у него нет прототипа), который считается устаревшей функцией в C11 (и C89, C99 для этого пункт) стандарт. Если вызывается такая функция, по аргументам аргументов по умолчанию делаются аргументы: int передается как есть, но float продвигается до double.

Поскольку ваша действительная функция ожидает аргументов (int, float), а не (int, double), это приведет к поведению undefined. Даже если ваша функция ожидала, что (int, double), но y было целым числом, или вы сказали, что вы вызвали его с помощью function1(0, 0); вместо function(0, 0.0);, ваша программа по-прежнему будет иметь поведение undefined. К счастью, GCC 5.2.1 замечает, что объявление и определение function1 противоречивы:

% gcc test.c
test.c:9:5: error: conflicting types for ‘function1’
 int function1(int x, float y) {
     ^
test.c:9:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration
 int function1(int x, float y) {
 ^
test.c:1:5: note: previous declaration of ‘function1’ was here
 int function1();
     ^
test.c:12:5: error: conflicting types for ‘function2’
 int function2(int x, float y) {
     ^
test.c:12:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration
 int function2(int x, float y) {
 ^
test.c:2:5: note: previous declaration of ‘function2’ was here
 int function2();
     ^

и компилятор выходит с кодом ошибки, тогда как мой tcc скомпилирует его с радостью, без диагностики, ничего. Он просто создает сломанный код. То же самое верно, если бы у вас было объявление в файле заголовка и определение в другом компиляторе, которое не включало это объявление.


Теперь, если компилятор не обнаружит этот случай, во время выполнения может произойти что-либо, как и ожидалось для поведения undefined.

Например, предположим, что аргументы переданы в стек; на 32-разрядных процессорах int и float может поместиться в 4 байта, тогда как double может быть 8 байтов; вызов функции затем будет толкать x как int и y как double, даже если он был float - всего, что вызывающий пользователь нажал бы 12 байтов, а вызываемый ожидал бы только 8.

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

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

Ответ 3

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

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

здесь приведены соответствующие цитаты из стандарта C (6.5.2.2 вызовы функций)

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

6 Если выражение, обозначающее вызываемую функцию, имеет тип, который не включает прототип, целые рекламные акции выполняются на каждый аргумент и аргументы, имеющие тип float, двойной. Они называются рекламными акциями по умолчанию. Если количество аргументов не равно числу параметров, поведение undefined. Если функция определена с типом, который включает прототип, и либо прототип заканчивается многоточием (,...) или типы аргументов после продвижения по службе не являются совместим с типами параметров, поведение undefined. Если функция определена с типом, который не включают прототип и типы аргументов после продвижения по службе не совместимы с параметрами после продвижения по службе, поведение undefined, за исключением следующих случаев:

- один продвинутый тип представляет собой целочисленный тип со знаком, другой способ продвижения - соответствующий целочисленный тип без знака, а значение - представляемые в обоих типах;

- оба типа являются указателями на квалифицированные или неквалифицированные версии тип символа или пустота.

Что касается ваших фрагментов кода, то если второй параметр имел тип double, тогда код был бы корректно сформирован. Однако, поскольку второй параметр имеет тип float, но соответствующий аргумент будет повышаться до типа double, тогда первый фрагмент кода имеет поведение undefined.

Ответ 4

В первом случае main() выполняет целые акции по каждому аргументу и float -to- double. Они называются "рекламные акции по умолчанию". Таким образом, вы, вероятно, в конечном итоге вызываете функции неправильно, передавая int и a double, если функции ожидают int и float.

См. Активные предложения по умолчанию в вызовах функций C и ответы для более подробной информации.