Почему компилируется функция без параметров (по сравнению с фактическим определением функции)?


388

Я только что натолкнулся на чей-то C-код, который меня смущает, почему он компилируется. Есть два момента, которые я не понимаю.

Во-первых, прототип функции не имеет параметров по сравнению с фактическим определением функции. Во-вторых, параметр в определении функции не имеет типа.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Почему это работает? Я проверил его в нескольких компиляторах, и он отлично работает.


77
Это K & R C. Мы написали подобный код в 1980-х годах, прежде чем появились полнофункциональные прототипы.
hughdbrown

6
gcc предупреждает с -Wstrict-prototypesобоими int func()и int main(): xc: 3: warning: объявление функции не является прототипом. Вы должны объявить , main()как main(void)и.
Йенс

3
@Jens Почему вы отредактировали вопрос? Вы, кажется, упустили суть ...
dlras2

1
Я просто сделал неявный int явным. Как это упустить момент? Я считаю, что дело в том, почему int func();совместим с int func(arglist) { ... }.
Йенс

3
@MatsPetersson Это неправильно. C99 5.1.2.2.1 прямо противоречит вашей заявке и утверждает, что версия без аргументов int main(void).
Йенс

Ответы:


271

Все остальные ответы верны, но только для завершения

Функция объявляется следующим образом:

  return-type function-name(parameter-list,...) { body... }

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

имя-функции - это имя функции.

Параметр-список - это список параметров, которые функция принимает через запятую. Если параметры не заданы, функция не принимает и должна быть определена с пустым набором скобок или ключевым словом void. Если тип переменной не находится перед переменной в списке параметров, то предполагается использование int . Массивы и функции не передаются в функции, но автоматически преобразуются в указатели. Если список заканчивается многоточием (, ...), то количество параметров не устанавливается. Примечание: заголовок stdarg.h может использоваться для доступа к аргументам при использовании многоточия.

И снова ради полноты. Из спецификации C11 6: 11: 6 (стр: 179)

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


2
«Если тип переменной не находится перед переменной в списке параметров, то предполагается использование int». Я вижу это в предоставленной вами ссылке, но не могу найти ее ни в одном стандартном c89, c99 ... Можете ли вы предоставить другой источник?
Годайго

«Если параметры не заданы, то функция не принимает и должна быть определена с пустым набором скобок», звучит противоречащим ответу Тони Льва, но я только что узнал, что ответ Тони Льва верен нелегко.
Якун

160

В С func()означает, что вы можете передать любое количество аргументов. Если вы не хотите аргументов, вы должны объявить как func(void). Тип, который вы передаете своей функции, если не указан, по умолчанию int.


3
На самом деле нет единого типа по умолчанию для int. На самом деле все аргументы по умолчанию имеют значение int (даже возвращаемый тип). Это совершенно нормально для вызова func(42,0x42);(где все вызовы должны использовать форму двух аргументов).
Йенс

1
В Си довольно часто для неопределенных типов значение по умолчанию равно int, например, когда вы объявляете переменную: unsigned x; Какого типа переменная х? Получается его неподписанный int
bitek

4
Я бы сказал, что неявный int был обычным явлением в старину. Это, конечно, уже не так, и в C99 он был удален из C.
Jens

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

@mnemonicflow, который не является «неопределенным типом по умолчанию для int»; unsignedэто просто другое имя для того же типа, что и unsigned int.
Хоббс

58

int func();является устаревшим объявлением функции со времен, когда не было стандарта C, т.е. дней K & R C (до 1989 года, когда был опубликован первый стандарт "ANSI C").

Помните, что в K & R C не было прототипов, а ключевое слово voidеще не было изобретено. Все, что вы могли сделать, это сообщить компилятору о типе возвращаемого значения функции. Пустой список параметров в K & R C означает «неопределенное, но фиксированное» количество аргументов. Фиксированный означает, что вы должны вызывать функцию с одинаковым количеством аргументов каждый раз (в отличие от функции с переменным числом, например printf, где число и тип могут варьироваться для каждого вызова).

Многие компиляторы будут диагностировать эту конструкцию; в частности gcc -Wstrict-prototypes, вам скажут, что «объявление функции не является прототипом», что очень важно, потому что выглядит как прототип (особенно если вы отравлены C ++!), но это не так. Это объявление типа возврата K & R C старого стиля.

Правило большого пальца. Никогда не оставляйте пустое объявление списка параметров пустым, используйте int func(void)для конкретности. Это превращает объявление возвращаемого типа K & R в правильный прототип C89. Компиляторы счастливы, разработчики счастливы, статические контролеры счастливы. Те, кто вводит в заблуждение ^ W ^ Wfond из C ++, могут съежиться, потому что им нужно вводить дополнительные символы, когда они пытаются использовать свои навыки иностранного языка :-)


1
Пожалуйста, не связывайте это с C ++. Здравый смысл заключается в том, что пустой список аргументов означает отсутствие параметров , и люди во всем мире не заинтересованы иметь дело с ошибками, допущенными парнями из K & R около 40 лет назад. Но эксперты из комитетов продолжают тащить странные решения - в C99, C11.
pfalcon

1
@pfalcon Здравый смысл довольно субъективен. Что здравый смысл для одного человека, просто безумие для других. Профессиональные программисты на Си знают, что пустые списки параметров указывают на неопределенное, но фиксированное количество аргументов. Изменение этого, вероятно, сломало бы много реализаций. Обвиняющие комитеты в том, что они избегают молчаливых перемен, лают не то дерево, имхо.
Дженс

53
  • Пустой список параметров означает «любые аргументы», поэтому определение не является неправильным.
  • Отсутствующий тип предполагается int.

Я бы посчитал, что в любой сборке, в которой это происходит, не хватает настроенного уровня предупреждения / ошибки, хотя нет никакого смысла в том, чтобы это учитывало фактический код.


30

Это объявление и определение функции в стиле K & R. От стандарта C99 (ISO / IEC 9899: TC3)

Раздел 6.7.5.3 Деклараторы функций (включая прототипы)

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

Раздел 6.11.6 Объявление функций

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

Раздел 6.11.7 Определения функций

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

Какой старый стиль означает K & R стиль

Пример:

Декларация: int old_style();

Определение:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}

16

C предполагает, intчто тип не указан в типе возвращаемого значения функции и списке параметров . Только для этого правила возможны следующие странные вещи.

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

int func(int param) { /* body */}

Если его прототип вы пишете

int func(int param);

В прототипе вы можете указать только тип параметров. Имя параметра не обязательно. Так

int func(int);

Также, если вы не указываете тип параметра, но имя intпринимается как тип.

int func(param);

Если вы идете дальше, то тоже работает.

func();

Компилятор предполагает, int func()когда вы пишете func(). Но не помещайте func()в тело функции. Это будет вызов функции


3
Пустой список параметров не связан с неявным типом int. int func()не является неявной формой int func(int).
Джон Варфоломей

11

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

В Old-C, как и в ANSI-C, « нетипизированный формальный параметр », возьмите измерение вашего рабочего регистра или возможности глубины команд (теневые регистры или совокупный цикл команд), в 8-битном MPU будет int16, в 16-битном MPU и так далее будет int16 и так далее, в случае, если 64-битные архитектуры могут выбрать компиляцию таких параметров, как: -m32.

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

В других случаях для некоторых микропроцессорных архитектур настраиваемые компиляторы ANSI использовали некоторые из этих старых функций для оптимизации использования кода, заставляя расположение этих «нетипизированных формальных параметров» работать внутри или вне рабочего регистра, сегодня вы получаете почти то же самое с использованием «volatile» и «register».

Но следует отметить, что самые современные компиляторы не делают различий между двумя типами объявления параметров.

Примеры компиляции с gcc под linux:

main.c

main2.c

main3.c  
В любом случае, утверждение прототипа локально не имеет смысла, потому что нет вызова без параметров, ссылка на этот прототип будет бесполезной. Если вы используете систему с «нетипизированным формальным параметром», для внешнего вызова перейдите к созданию декларативного типа данных прототипа.

Нравится:

int myfunc(int param);

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

1
Я почти уверен, что всегда указывается неопределенный тип возвращаемого значения int, обычно это размер рабочего регистра или 16 бит, в зависимости от того, что меньше.
суперкат

@supercat +1 Идеальный вывод, ты прав! Это именно то, что я обсуждаю выше, компилятор, спроектированный по умолчанию для конкретной архитектуры, всегда отражает размер рабочего регистра рассматриваемого CPU / MPU, поэтому во встроенной среде существуют различные стратегии повторного набора ОС реального времени, на уровень компилятора (stdint.h) или на уровень переносимости, который делает переносимую ту же ОС во многих других архитектурах и получается путем выравнивания определенных типов CPU / MPU (int, long, long long и т. д.) с общими типами систем u8, u16, u32.
RTOSkit

@supercat Без типов элементов управления, предлагаемых операционной системой или конкретным компилятором, вам нужно немного уделить внимание времени разработки, и вы сделаете так, чтобы все «нетипизированные назначения» были согласованы с дизайном вашего приложения, прежде чем вы удивитесь, обнаружив « 16-битный int ", а не" 32-битный int ".
RTOSkit

@RTOSkit: Ваш ответ предполагает, что типом по умолчанию на 8-битном процессоре будет Int8. Я вспоминаю пару компиляторов C-ish для архитектуры бренда PICmicro, где это имело место, но я не думаю, что что-либо отдаленно напоминающее стандарт C, когда-либо допускало intтип, который не был способен одновременно удерживать все значения в диапазоне - От 32767 до +32767 (примечание -32768 не требуется), а также может содержать все charзначения (это означает, что если значение charравно 16 битам, оно должно быть подписано или intдолжно быть больше).
суперкат

5

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

компилируя вашу программу, используя gcc foo.c -Wextra:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

странно -Wextraне улавливает это clang(не распознает -Wmissing-parameter-typeпо какой-то причине, может быть, для исторических, упомянутых выше), но -pedanticделает:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

А для проблемы с прототипом, как уже было сказано выше, это int func()относится к произвольным параметрам, если вы не определите их явно, так как int func(void)это даст вам ошибки, как и ожидалось:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

или clangкак:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.

3

Если объявление функции не имеет параметров, т. Е. Пусто, оно принимает неопределенное количество аргументов. Если вы хотите, чтобы он не принимал аргументов, измените его на:

int func(void);

1
«Если у прототипа функции нет параметров» Это не прототип функции, а просто объявление функции.
Effeffe

@effeffe исправил это. Я не понимал, что этот вопрос привлечет много внимания;)
ПП

3
Разве «прототип функции» не является просто альтернативным (возможно, старомодным) выражением для «объявления функции»?
Джорджио

@ Джорджио ИМО, оба верны. «прототип функции» должен соответствовать «определению функции». Я, вероятно, думаю, что effeffe означал объявление в вопросе, и что я имел в виду, в моем ответе
ПП

@ Джорджио нет, объявление функции производится значением возвращаемого типа, идентификатором и списком необязательных параметров; Прототипы функций - это объявления функций со списком параметров.
effeffe

0

Вот почему я обычно советую людям компилировать их код:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Эти флаги поддерживают несколько вещей:

  • -Wmissing-variable-объявлений: Невозможно объявить нестатическую функцию без предварительного получения прототипа. Это повышает вероятность того, что прототип в заголовочном файле совпадает с фактическим определением. Кроме того, он принудительно добавляет ключевое слово static к функциям, которые не должны отображаться публично.
  • -Wstrict-variable-объявлений: прототип должен правильно перечислять аргументы.
  • Определение стиля: само определение функции должно также правильно перечислять аргументы.

Эти флаги также используются по умолчанию во многих проектах с открытым исходным кодом. Например, во FreeBSD эти флаги включены при сборке с WARNS = 6 в вашем Makefile.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.