Чому функція без параметрів (порівняно з фактичним визначенням функції) складається?


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: попередження: декларація функції не є прототипом. Ви повинні оголосити , main()як main(void)і.
Єнс

3
@Jens Чому ви редагували питання? Ви, здається, пропустили
справу

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 .

function-name - це назва функції.

list-list - це список параметрів, які функція приймає розділені комами. Якщо параметри не задані, функція не приймає жодної і повинна визначатися з порожнім набором дужок або з ключовим словом void. Якщо перед змінною у списку параметрів немає типу змінної, то int передбачається . Масиви та функції не передаються функціям, але автоматично перетворюються на покажчики. Якщо список припиняється еліпсисом (, ...), то не встановлена ​​кількість параметрів. Примітка: заголовок stdarg.h може використовуватися для доступу до аргументів при використанні еліпсису.

І знову заради повноти. З специфікації C11 6: 11: 6 (сторінка: 179)

Використання функції declarators з порожніми дужками (НЕ прототип формат declarators типу параметра) є застарілим особливість .


2
"Якщо перед змінною у списку параметрів немає типу змінної, то int передбачається." Я бачу це у вашому наданому посиланні, але я не можу його знайти в жодному стандартному c89, c99 ... Чи можете ви надати інше джерело?
Godaygo

"Якщо параметри не задані, функція не приймає жодних, і їх слід визначати з порожнім набором дужок", це суперечить відповіді Тоні Лева, але я щойно дізнався, що відповідь Тоні Лева - це правильний шлях.
Якун

160

В C func()означає, що ви можете передавати будь-яку кількість аргументів. Якщо ви не хочете жодних аргументів, тоді ви повинні оголосити як func(void). Тип, який ви передаєте вашій функції, якщо не вказано значення за замовчуванням int.


3
Насправді не існує єдиного типу, дефолтний до int. Насправді всі аргументи за замовчуванням до int (навіть тип повернення). Цілком нормально дзвонити func(42,0x42);(де всі дзвінки повинні використовувати форму двох арг).
Єнс

1
У C цілком часто зустрічається невизначений тип за замовчуванням для int, наприклад, наприклад, коли ви оголошуєте змінну: unsigned x; Якого типу є змінна х? Виявляється, його
неподписаний

4
Я б сказала, що неявна інта була поширеною в старі часи. Це, звичайно, вже не було, і в C99 його зняли з C.
Jens

2
Можливо, варто відзначити, що ця відповідь стосується прототипів функцій із порожніми списками параметрів, але не визначень функцій із порожніми списками параметрів.
аутист

@mnemonicflow, що не є "невизначеним типом, дефолтом до int"; unsignedце лише інша назва того ж типу, що і unsigned int.
варення

58

int func();є застарілою декларацією функції від днів, коли не було стандарту С, тобто днів 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 Здоровий глузд є досить суб'єктивним. Що є здоровим глуздом для однієї людини - це просто божевілля для інших. Професійні програмісти C знають, що порожні списки параметрів вказують на не визначене, але фіксовану кількість аргументів. Зміна, яка, ймовірно, порушить багато реалізацій. Звинувачуючи комітети у уникненні мовчазних змін, це гавкає неправильне дерево, ІМХО.
Єнс

53
  • Порожній список параметрів означає "будь-які аргументи", тому визначення не є помилковим.
  • Відсутній тип вважається таким int.

Я вважаю, що будь-яка збірка, яка передає це, не вистачає налаштованого рівня попередження / помилки, однак немає сенсу в тому, що це дозволяє реальному коду.


30

Це оголошення та визначення функції стилю K&R . Від стандарту C99 (ISO / IEC 9899: TC3)

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

Список ідентифікаторів оголошує лише ідентифікатори параметрів функції. Порожній список декларатора функції, який є частиною визначення цієї функції, вказує, що функція не має параметрів. Порожній список у деклараторі функцій, який не є частиною визначення цієї функції, вказує, що інформація про число чи типи параметрів не надається. (Якщо обидва типи функцій "старого стилю", типи параметрів не порівнюються.)

Розділ 6.11.6 Декларатори функцій

Використання деклараторів функцій з порожніми дужками (не декларатори типу прототипу формату) є застарілою ознакою.

Розділ 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 налаштовували, використовували деякі з цих старих функцій для оптимізації використання коду, змушуючи розташування цих "нетипізованих формальних параметрів" працювати в робочому регістрі або поза ним, сьогодні ви отримуєте майже те саме із застосуванням "летючого" та "реєструвати".

Але слід зазначити, що найсучасніші компілятори не роблять різниці між двома типами декларації параметрів.

Приклади компіляції з gcc під Linux:

main.c

main2.c

main3.c  
У будь-якому випадку заява прототипу локально не приносить користі, оскільки немає виклику без параметрів, посилання на цей прототип буде відхилено. Якщо ви використовуєте систему з "нетиповим формальним параметром", для зовнішнього виклику переходите до створення декларативного типу даних прототипу.

Подобається це:

int myfunc(int param);

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

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

@supercat +1 Ідеальна дедукція, ви праві! Це саме те, що я обговорюю вище, компілятор, розроблений за замовчуванням для конкретної архітектури, завжди відображає розмір робочого реєстру ЦП / МПУ, про який йдеться. Отже, у вбудованій середовищі існують різні стратегії повторного набору тексту на ОС в реальному часі, в рівень компілятора (stdint.h) або в шар переносимості, що робить переносну ту саму ОС у багатьох інших архітектурах, отриману шляхом вирівнювання конкретних типів CPU / MPU (int, long, long long тощо) із загальною системою типів u8, u16, u32.
RTOSkit

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

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

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
"Якщо у прототипу функції немає параметрів" Це не прототип функції, а лише декларація функції.
efeffe

@effeffe виправив це. Я не усвідомлював, що це питання приверне багато уваги;)
ПП

3
Чи не "прототип функції" є лише альтернативним (можливо, старомодним) виразом для "оголошення функції"?
Джорджіо

@Giorgio IMO, обидва вірні. "прототип функції" повинен відповідати "визначенню функції". Я, напевно, думаю, що efeffe означало декларацію у запитанні, і що я мав на увазі у своїй відповіді.
ПП

@Giorgio ні, декларація функції робиться значенням типу повернення, ідентифікатором та необов'язковим списком параметрів; Прототипи функцій - це оголошення функції зі списком параметрів.
efeffe

0

Ось чому я зазвичай раджу людям складати свій код із:

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

Ці прапори застосовують кілька речей:

  • -Визначення змінної-декларації: неможливо оголосити нестатичну функцію без отримання прототипу спочатку. Це робить більш імовірним, що прототип у файлі заголовка збігається з фактичним визначенням. Крім того, він примушує додавати статичне ключове слово до функцій, які не повинні бути видимими для всіх.
  • -Оголошення змінної змінної: прототип повинен правильно перелічити аргументи.
  • -Wold-style-definition: саме визначення функції також має належним чином перераховувати аргументи.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.