Чому gcc дозволяє передавати аргументи функції, визначеної без аргументів?


75

Я не розумію, чому цей код компілюється?

gcc навіть не видає попередження про те, що я передаю занадто багато аргументів foo (). Це очікувана поведінка?


21
Ласкаво просимо до мови C, це частина застарілого стандарту. FYI не підтримується в C ++.
Стів-о

9
Зазвичай ти можеш надихнути компілятор на попередження, якщо void foo(void)
оголосиш

6
@HansPassant: Якщо ви оголосите це як void foo(void), компілятор зазвичай повинен видавати помилку, а не просто попередження (хоча офіційне формулювання просто вимагає "діагностики").
Джеррі Трун

3
add -Wall при його компіляції
pyCthon

1
@reddragon див. мою відповідь .
ecatmur

Відповіді:


85

У C функція, оголошена з порожнім списком параметрів, приймає довільну кількість аргументів під час виклику, які підлягають звичайним арифметичним просуванням. Обов'язок абонента - забезпечити відповідність аргументів для визначення функції.

Щоб оголосити функцію, яка приймає нуль аргументів, вам потрібно написати void foo(void);.

Це з історичних причин; спочатку у функцій C не було прототипів, оскільки C еволюціонував із мови B , нетипової мови. Коли були додані прототипи, оригінали безтипових декларацій залишили в мові для зворотної сумісності.

Щоб gcc попереджав про порожні списки параметрів, використовуйте -Wstrict-prototypes:

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


53

З застарілих причин, оголошення функції ()за допомогою списку параметрів по суті означає "з’ясувати параметри, коли функція викликається". Щоб вказати, що функція не має параметрів, використовуйте (void).

Редагувати: Я відчуваю, що набираю репутацію в цій проблемі через те, що старий. Щоб ви, діти, знали, яким було програмування, ось моя перша програма . (Не C; це показує вам, з чим нам доводилось працювати до цього.)


2
Стривай, ти це серйозно? Чому ти коли-небудь це робиш?
Вихід

5
Правила типу, орієнтація об’єкта, інкапсуляція, перевантаження та інші сучасні мовні особливості не розвинулися за одну ніч. Десятиліття тому мови програмування були набагато примітивнішими. І експериментальний. C був авансом у порівнянні з тим, що було до нього, і причини сильного набору параметрів функції або способу їх реалізації не були зрозумілими. Основною мотивацією того часу було надання програмістам простих способів робити потужні речі. Тоді потреба у використанні сильного набору тексту для зменшення помилок не була такою великою мотивацією.
Eric Postpischil

4
@Drise: ти не хотів би, більше не. Однак до стандарту 1989 року мова C не підтримувала декларації прототипів (де оголошено кількість та типи параметрів); ви можете лише вказати тип повернення функції в декларації. Існує багато застарілого коду, який зламався б, якщо ви змінили правила для порожніх списків параметрів у деклараціях, тому він все ще підтримується, але новий код завжди повинен використовувати синтаксис прототипу при оголошенні функцій.
Джон Боде

3
З точки зору безпеки типу, ранній C прогресував лише над B. Було багато безпечно набраних мов (Simula, Pascal, навіть Algol), які не мали проблем із сильним набором параметрів функції. C виграв завдяки більшій ефективності та спрощенню реалізації на мінікомп'ютерах з обмеженими ресурсами, але компроміси, що стосувалися цього, були очевидними на той час.
ecatmur

1
@ John Bode є правильним стосовно C. C. IIRC до 1989 р. Його також називають традиційним C. Ще одна чудова "особливість", дозволена використання int для char. Моїм першим компілятором C був традиційний C, і він навчив мене любити ANSI C.
jqa

10

в C, цей код не порушує обмеження (це було б, якби це було визначено у його прототипі-формі за допомогою void foo(void) {/*...*/} ), і оскільки порушення обмежень не існує, компілятор не зобов'язаний видавати діагностику.

Але ця програма має невизначену поведінку згідно з наступними правилами C:

Від:

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

fooфункція не забезпечує прототип.

Від:

(C99, 6.5.2.2p6) "Якщо вираз, що позначає викликану функцію, має тип, який не включає прототип [...] Якщо кількість аргументів не дорівнює кількості параметрів, поведінка не визначена."

foo(str)виклик функції не визначено поведінку.

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


6

Як стандарт C99 (6.7.5.3), так і стандарт C11 (6.7.6.3):

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

Оскільки оголошення foo є частиною визначення, декларація визначає, що foo приймає 0 аргументів, тому виклик foo (str) є принаймні морально неправильним. Але, як описано нижче, в C є різні ступені "неправильного", і компілятори можуть відрізнятися в тому, як вони поводяться з певними видами "неправильного".

Щоб взяти дещо простіший приклад, розглянемо таку програму:

Якщо я скомпілюю вищезазначене за допомогою Clang:

Якщо я компілюю за допомогою gcc 4.8, я не отримую жодних помилок або попереджень, навіть із -Wall. Попередня відповідь пропонувала використовувати -Wstrict-prototypes, де правильно повідомляється, що визначення f не є у формі прототипу, але насправді справа не в цьому. Стандарт (и) C дозволяють визначити функцію у формі, що не є прототипом, наприклад, наведеній вище, і Стандарти чітко вказують, що це визначення вказує, що функція приймає 0 аргументів.

Зараз існує обмеження (C11, розділ 6.5.2.2):

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

Однак це обмеження не застосовується в цьому випадку, оскільки тип функції не включає прототип. Але ось наступне твердження в розділі семантики (не "обмеження"), яке дійсно застосовується:

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

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

Тому я думаю, що відповідь на запитання, чому gcc це дозволяє ?, полягає в тому, що gcc не зобов’язана повідомляти про що-небудь, оскільки це не є порушенням обмежень. Більше того, gcc не претендує повідомляти про всі види невизначеної поведінки, навіть із -Wall або -Wpedantic. Це невизначена поведінка, що означає, що реалізація може вибрати, як з цим боротися, а gcc вирішила скомпілювати її без попереджень (і, очевидно, вона просто ігнорує аргумент).


За днів до C89 більшість компіляторів C ігнорували будь-які невикористані аргументи і дозволяли абонентам пропускати аргументи, пов'язані з невикористаними параметрами. Перевірка належних конвенцій про виклики, як правило, корисна, але деякі існуючі API можуть вимагати передачі функцій різної кількості та типу аргументів. Звичайний спосіб роботи з ..., але значення параметрів для ...функцій отримуються інакше, ніж для "звичайних" функцій.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.