Чи корисно в C99 коли-небудь “inline” без “static” чи “extern”?


96

Коли я намагаюся створити цей код

inline void f() {}

int main()
{
    f();
}

за допомогою командного рядка

gcc -std=c99 -o a a.c

Я отримую помилку компонувальника (невизначене посилання на f). Помилка зникає, якщо я використовую static inlineабо extern inlineзамість просто inline, або якщо я компілюю за допомогою -O(тому функція насправді вбудована).

Здається, така поведінка визначена у пункті 6.7.4 (6) стандарту C99:

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

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

Хіба така поведінка не зовсім хитра? Чи корисно коли-небудь визначати функцію inlineбез staticабо externв C99? Я щось пропускаю?

Короткий зміст відповідей

Звичайно, мені чогось не вистачало, і поведінка не хитра. :)

Як пояснює Немо , ідея полягає в тому, щоб дати визначення функції

inline void f() {}

у файлі заголовка і лише декларація

extern inline void f();

у відповідному файлі .c. Тільки externдекларація ініціює генерацію зовнішнього видимого двійкового коду. І inlineу файлі .c насправді немає ніякої користі - це корисно лише в заголовках.

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


прикордонне дублікат stackoverflow.com/questions/5369888 / ...
Earlz


@Earlz: Дякую за посилання. Моє питання стосується обгрунтування цитованого абзацу стандарту, та чи існують випадки використання для inlineбез staticі extern, однак. На жаль, жодне з цих питань не розглядається в цьому питанні.
Sven Marnach

@Nemo: Я прочитав це питання та відповіді до того, як опублікував своє. Знову ж таки, я не питаю "як це поводиться?" а скоріше "в чому ідея такої поведінки"? Я майже впевнений, що мені чогось тут не вистачає.
Sven Marnach

1
так, тому я сказав "прикордонний". Я особисто думаю, що це задає зовсім інше питання
Earlz,

Відповіді:


40

Насправді ця чудова відповідь також відповідає на ваше запитання, я думаю:

Що робить зовнішній вбудований?

Ідея полягає в тому, що "inline" можна використовувати у файлі заголовка, а потім "extern inline" у файлі .c. "extern inline" - це саме те, як ви вказуєте компілятору, який об'єктний файл повинен містити (видимий зовні) згенерований код.

[оновити, докладно]

Я не думаю, що у файлі .c використовується "inline" (без "static" або "extern"). Але у файлі заголовка це має сенс, і він вимагає відповідного оголошення "extern inline" у якомусь файлі .c, щоб насправді генерувати автономний код.


1
Дякую, я починаю розуміти ідею!
Sven Marnach

8
Так, я ніколи цього не розумів сам, тож дякую за запитання :-). Це дивно тим, що не-зовнішнє "вбудоване" визначення входить у заголовок (але не обов'язково призводить до генерування коду взагалі), тоді як "зовнішнє вбудоване" оголошення йде у файл .c і фактично змушує код генеруватися.
Немо

3
Чи означає це, що я пишу inline void f() {}в заголовку та extern inline void f();у файлі .c? Отже, фактичне визначення функції йде в заголовку, а файл .c містить у цьому випадку лише декларацію, в зворотному порядку звичайного порядку?
Sven Marnach

1
@endolith: Я не розумію вашого запитання. Ми обговорюємо inline без static або extern. Звичайно, static inlineце добре, але це не те, що стосується цього питання та відповіді.
Немо

2
@MatthieuMoy Вам потрібно використовувати -std=c99замість -std=gnu89.
a3f

27

З самого стандарту (ISO / IEC 9899: 1999):

Додаток J.2 Невизначена поведінка

  • ...
  • Функція із зовнішнім зв'язком оголошується inlineспецифікатором функції, але вона також не визначена в тій самій одиниці перекладу (6.7.4).
  • ...

Комітет C99 написав обгрунтування , і в ньому сказано:

6.7.4 Специфікатори функцій

Нова особливість C99:inline ключове слово, запозичені з C ++, є функція-специфікатор , який може бути використаний тільки в оголошенні функції. Це корисно для оптимізації програми, яка вимагає, щоб визначення функції було видно на місці виклику. (Зверніть увагу, що Стандарт не намагається вказати характер цих оптимізацій.)

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

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

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

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

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

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Оскільки реалізація може використовувати вбудоване визначення для одного із викликів saddrта використовувати зовнішнє визначення для іншого, операція рівності не гарантується оцінювати як 1 (істина). Це показує, що статичні об'єкти, визначені у вбудованому визначенні, відрізняються від відповідного їм об'єкта у зовнішньому визначенні. Це мотивувало обмеження навіть щодо визначення не- constоб'єкта цього типу.

Вбудовування було додано до стандарту таким чином, що його можна реалізувати за допомогою існуючої технології компонування, а підмножина вкладання C99 сумісна з C ++. Це було досягнуто завдяки вимогам, щоб точно одна одиниця перекладу, що містить визначення вбудованої функції, була вказана як та, яка забезпечує зовнішнє визначення функції. Оскільки ця специфікація складається просто з оголошення, в якому або бракує inlineключового слова, або містить обидва inlineтаextern воно також буде прийняте перекладачем на C ++.

Вбудовування в C99 розширює специфікацію C ++ двома способами. По-перше, якщо функція оголошена inlineв одній одиниці перекладу, її не потрібно оголошувати inlineв будь-якій іншій одиниці перекладу. Це дозволяє, наприклад, функцію бібліотеки, яка повинна бути вбудована в бібліотеку, але доступна лише через зовнішнє визначення в іншому місці. Альтернатива використання функції обгортки для зовнішньої функції вимагає додаткового імені; і це також може негативно вплинути на ефективність роботи, якщо перекладач насправді не виконує вбудовану заміну.

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

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

#define abs(x) __builtin_abs(x)

або інші непереносні механізми для включення стандартних функцій бібліотеки.


Дякую, це дуже детально - і майже настільки ж читабельно, як і сам стандарт. :-) Я знала, що, мабуть, чогось мені не вистачає. Не могли б ви дати посилання, звідки ви це взяли?
Sven Marnach

Мені просто спало на
Свен Марнах,

@Sven: Я запозичив URL-адресу з вашого пошуку та ввів її у відповідь. Документ, яким я скористався, був копією обгрунтування, яке я раніше зберігав (у 2005 р.), Але він також був V5.10.
Джонатан Леффлер,

4
Знову дякую. Найцінніше, що я отримав із відповіді, - це те, що існує документ, що містить обгрунтування стандарту C99 (і, ймовірно, є документи і для інших стандартів). Поки відповідь Немо дав мені рибу, ця навчила мене ловити рибу.
Sven Marnach

0

> Я отримую помилку компонувальника (невизначене посилання на f)

Працює тут: Linux x86-64, GCC 4.1.2. Може бути помилка у вашому компіляторі; Я не бачу нічого в цитованому абзаці зі стандарту, що забороняє дану програму. Зверніть увагу на використання if, а не iff .

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

Отже, якщо ви знаєте поведінку функції fі хочете викликати її в щільному циклі, ви можете скопіювати та вставити її визначення в модуль, щоб запобігти викликам функції; або , ви можете надати визначення, яке для цілей поточного модуля є еквівалентним (але пропускає перевірку вводу або будь-яку оптимізацію, яку ви можете собі уявити). Однак програма для компіляції може замість цього оптимізувати розмір програми.


2
Стандарт зазначає, що компілятор завжди може припустити, що існує зовнішня функція з тим самим іменем, і викликати її замість вбудованої версії, тому я не думаю, що це помилка компілятора. Я спробував з gcc 4.3, 4.4 та 4.5, все дають помилку компонувальника.
Sven Marnach
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.