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

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

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 в С99? Я что-то пропустил?

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

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

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

inline void f() {}

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

extern inline void f();

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

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

3 ответа

Решение

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

внешняя линия

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

[обновить, разработать]

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

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

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

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

Комитет С99 написал Обоснование, в котором говорится:

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)

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

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

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

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

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

Другие вопросы по тегам