Полезны ли «inline» без «static» или «extern» в C99?

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без C99 staticили externв нем? Я что-то упускаю?

Резюме ответов

Конечно, мне чего-то не хватало, и поведение не дурацкое. :)

Как объясняет Немо , идея состоит в том, чтобы поместить определение функции

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
да, поэтому я сказал «пограничный». Я лично думаю, что это задает совершенно другой вопрос
Эрлз

Ответы:

40

На самом деле этот отличный ответ также отвечает на ваш вопрос, я думаю:

Что делает extern inline?

Идея состоит в том, что «inline» можно использовать в файле заголовка, а затем «extern inline» в файле .c. «extern inline» - это то, как вы указываете компилятору, какой объектный файл должен содержать (видимый извне) сгенерированный код.

[обновить, уточнить]

Я не думаю, что в файле .c есть какой-то смысл для "inline" (без "static" или "extern"). Но в файле заголовка это имеет смысл, и для фактической генерации автономного кода требуется соответствующее объявление "extern inline" в каком-то файле .c.

Немо
источник
1
Спасибо, я начинаю понимать!
Sven Marnach
8
Да, я сам этого не понимал до сих пор, так что спасибо за вопрос :-). Это странно , что не-Экстерн «встроенный» определение идет в заголовке (но не обязательно приводит к каким - либо генерации кода на всех), в то время как «ехЬегп инлайн» Декларация идет в файл .c и фактически вызывает код быть сгенерирован.
Nemo
3
Значит ли это, что я пишу inline void f() {}в шапке и extern inline void f();в файле .c? Таким образом, фактическое определение функции находится в заголовке, а файл .c содержит простое объявление в этом случае в обратном порядке?
Sven Marnach
1
@endolith: Я не понимаю вашего вопроса. Мы обсуждаем inline без static или extern. Конечно, static inlineэто нормально, но этот вопрос и ответ не об этом.
Nemo
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во всех остальных единицах трансляции. Это позволяет, например, использовать библиотечную функцию, которая должна быть встроена в библиотеку, но доступна только через внешнее определение в другом месте. Альтернатива использования функции-оболочки для внешней функции требует дополнительного имени; и это также может отрицательно сказаться на производительности, если переводчик фактически не выполняет встроенную замену.

Во-вторых, требование, чтобы все определения встроенной функции были «точно такими же», заменяется требованием, чтобы поведение программы не зависело от того, реализован ли вызов с видимым встроенным определением или внешним определением функция. Это позволяет специализировать встроенное определение для его использования в конкретной единице перевода. Например, внешнее определение библиотечной функции может включать некоторую проверку аргументов, которая не требуется для вызовов, сделанных из других функций в той же библиотеке. Эти расширения действительно имеют некоторые преимущества; а программисты, заботящиеся о совместимости, могут просто соблюдать более строгие правила C ++.

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