Является ли «встроенный» без «статического» или «внешнего» полезным в C99? - PullRequest
90 голосов
/ 11 июня 2011

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

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

Ответы [ 3 ]

37 голосов
/ 11 июня 2011

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

внешний встроенный

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

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

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

24 голосов
/ 11 июня 2011

Из самого стандарта (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)

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

0 голосов
/ 11 июня 2011

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

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

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

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

...