Является ли "встроенным" без "статического" или "внешнего", когда-либо полезного в C99?

Когда я пытаюсь создать этот код

inline void f() {}

int main()
{
    f();
}

с помощью командной строки

gcc -std=c99 -o a a.c

Я получаю ошибку компоновщика (undefined ссылка на f). Ошибка исчезает, если я использую static inline или extern inline вместо просто inline, или если я скомпилирую с -O (поэтому функция фактически встроена).

Такое поведение, как представляется, определено в пункте 6.7.4 (6) стандарта C99:

Если все объявления области видимости для функции в блоке трансляции включают спецификатор функции inline без extern, тогда определение в этом блоке перевода является встроенным определением. Встроенное определение не предоставляет внешнего определения функции и не запрещает внешнее определение в другой единицы перевода. Встроенное определение предоставляет альтернативу внешнему определению, которое переводчик может использовать для реализации любого вызова функции в той же самой единице перевода. Не указано, использует ли вызов функции встроенное определение или внешнее определение.

Если я все правильно понимаю, блок компиляции с определенной функцией inline, как в приведенном выше примере, только последовательно компилируется, если есть также внешняя функция с тем же именем, и я никогда не знаю, принадлежит ли моя собственная функция или вызывается внешняя функция.

Разве это не совсем глупо? Полезно ли когда-либо определять функцию inline без static или extern в C99? Я что-то пропустил?

Сводка ответов

Конечно, я что-то упустил, и поведение не глупо.:)

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

inline void f() {}

в файле заголовка и только объявление

extern inline void f();

в соответствующем .c файле. Только объявление extern запускает генерацию внешнего видимого двоичного кода. И действительно, нет использования inline в .c файле - это полезно только в заголовках.

Как поясняет обоснование комитета C99, приведенное в ответе Джонатана, inline - это все, что связано с оптимизацией компилятора, требующей определения функции для видимости на сайт вызова. Это может быть достигнуто только путем помещения определения в заголовок, и, конечно, определение в заголовке не должно генерировать код каждый раз, когда это видно компилятору. Но поскольку компилятор не вынужден фактически встроить функцию, должно существовать внешнее определение.

Ответ 1

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

extern inline

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

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

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

Ответ 2

Из стандарта (ISO/IEC 9899: 1999):

Приложение J.2 Undefined Поведение

  • ...
  • Функция с внешней связью объявляется с помощью спецификатора функции inline, но не определяется также в той же самой системе перевода (6.7.4).
  • ...

Комитет C99 написал Rationale, и он говорит:

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

Новая функция C99: ключевое слово inline, адаптированное из С++, является спецификатором функции, который может использоваться только в объявлениях функций. Это полезно для оптимизации программ, которые требуют определение функции, которая должна быть видимой в месте вызова. (Обратите внимание, что стандарт не пытается указать природу этих оптимизаций.)

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

Вложение в C99 расширяет спецификацию С++ двумя способами. Во-первых, если объявлена ​​функция inline в одной единицы перевода, ее не нужно объявлять inline в каждой другой единицы перевода. Это позволяет, например, библиотечную функцию, которая должна быть встроена в библиотеку, но доступна только через внешнее определение в другом месте. Альтернатива использования оберточной функции для внешняя функция требует дополнительного имени; и это может также отрицательно сказаться на производительности если переводчик фактически не выполняет встроенную подстановку.

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

Обратите внимание, что реализациям не подходит встроенные определения стандарта библиотечные функции в стандартных заголовках, потому что это может сломать некоторый унаследованный код, который переопределяет стандартные функции библиотеки после включения их заголовков. Ключевое слово inlineпредназначенный только для того, чтобы предоставить пользователям портативный способ предложить встраивание функций. Поскольку стандартные заголовки не должны быть переносимыми, реализации имеют другие варианты в соответствии с:

#define abs(x) __builtin_abs(x)

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

Ответ 3

> Я получаю ошибку компоновщика (undefined ссылка на f)

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

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

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