Какая польза от ключевого слова inline в C?

Я прочитал несколько вопросов в stackru о inline в C, но до сих пор не ясно, об этом.

  1. static inline void f(void) {} не имеет практической разницы с static void f(void) {},
  2. inline void f(void) {} в C не работает как C++. Как это работает в C?
  3. Что на самом деле делает extern inline void f(void); делать?

Я никогда не находил применение inline ключевое слово в моих программах на C, и когда я вижу это ключевое слово в коде других людей, оно почти всегда static inline, в котором я не вижу разницы с просто static,

7 ответов

Решение

Примечание: когда я говорю о .c файлы и .h файлы в этом ответе, я полагаю, вы правильно выложили свой код, т.е. .c файлы включают только .h файлы. Различие в том, что .h файл может быть включен в несколько единиц перевода.

static inline void f(void) {} не имеет практической разницы с static void f(void) {},

В ISO C это правильно. Они идентичны по поведению (конечно, если вы не объявляете их по-разному в одном и том же TU!), Единственный практический эффект может заключаться в том, чтобы компилятор по-разному оптимизировал.

inline void f(void) {} в C не работает как C++. Как это работает в C? Что на самом деле делает extern inline void f(void); делать?

Это объясняется этим ответом, а также этой веткой.

В ISO C и C++ вы можете свободно использовать inline void f(void) {} в заголовочных файлах - хотя по разным причинам!

В ISO C он вообще не дает внешнего определения. В ISO C++ это обеспечивает внешнее определение; однако в C++ есть дополнительное правило (которого нет в C): если существует несколько внешних определений inline функции, то компилятор сортирует его и выбирает один из них.

extern inline void f(void); в .c файл в ISO C предназначен для сопряжения с использованием inline void f(void) {} в заголовочных файлах. Это приводит к тому, что внешнее определение функции выдается в этой единице перевода. Если вы этого не сделаете, тогда нет внешнего определения, и поэтому вы можете получить ошибку ссылки (не определено, является ли какой-либо конкретный вызов f ссылки на внешнее определение или нет).

Другими словами, в ISO C вы можете вручную выбрать, куда идет внешнее определение; или полностью исключить внешнее определение, используя static inline везде; но в ISO C++ компилятор выбирает, куда и куда направлять внешнее определение.

В GNU C все по-другому (подробнее об этом ниже).

Чтобы усложнить ситуацию дальше, GNU C++ позволяет писать static inline extern inline в коде C++... Я не хотел бы догадываться, что именно это делает

Я никогда не находил использование встроенного ключевого слова в моих программах на C, и когда я вижу это ключевое слово в коде других людей, оно почти всегда статично встроено

Многие программисты не знают, что делают, и просто собирают что-то, что, кажется, работает. Еще одним фактором здесь является то, что код, который вы просматриваете, мог быть написан для GNU C, а не ISO C.

В GNU C обычный inline ведет себя иначе, чем ISO C. На самом деле он испускает внешне видимое определение, поэтому наличие .h файл с равниной inline Функция, включенная из двух блоков перевода, вызывает неопределенное поведение.

Так что, если кодер хочет поставить inline Подсказка по оптимизации в GNU C, затем static inline необходимо. поскольку static inline работает как в ISO C, так и в GNU C, естественно, что люди согласились на это и увидели, что это работает без ошибок.

, в котором я не вижу разницы только со статикой.

Разница только в том, что мы хотим предоставить компилятору подсказку по оптимизации скорости. С современными компиляторами это лишнее.

Код A C можно оптимизировать двумя способами: для размера кода и для времени выполнения.

встроенные функции:

gcc.gnu.org говорит,

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

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

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


нестатический inline и статический inline

Снова ссылаясь на gcc.gnu.org,

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


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

И снова gcc.gnu.org говорит сам за себя:

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

Эта комбинация inline и extern имеет почти эффект макроса. Чтобы использовать его, нужно поместить определение функции в заголовочный файл с этими ключевыми словами, а другую копию определения (без встроенного и внешнего) в библиотечный файл. Определение в заголовочном файле приводит к тому, что большинство вызовов функции будут встроенными. Если какое-либо использование функции остается, они ссылаются на единственную копию в библиотеке.


Подвести итог:

  1. За inline void f(void){},inline Определение действительно только в текущей единице перевода.
  2. За static inline void f(void) {}Поскольку класс хранения static, идентификатор имеет внутреннюю связь и inline определение невидимо в других единицах перевода.
  3. За extern inline void f(void);Поскольку класс хранения externидентификатор имеет внешнюю связь, а встроенное определение также обеспечивает внешнее определение.

С 6.7.4 Спецификаторы функций в спецификациях C11

6 Функция, объявленная со спецификатором встроенной функции, является встроенной функцией. Создание функции как встроенной функции предполагает, что вызовы функции должны быть максимально быстрыми. 138) Степень эффективности таких предложений определяется реализацией. 139)

138) Используя, например, альтернативу обычному механизму вызова функции, такую ​​как встроенное замещение. Встроенная замена не является текстовой заменой и не создает новую функцию. Поэтому, например, при расширении макроса, используемого в теле функции, используется определение, которое оно имело в точке появления тела функции, а не там, где вызывается функция; и идентификаторы относятся к объявлениям в области видимости тела. Аналогично, функция имеет один адрес, независимо от количества встроенных определений, которые встречаются в дополнение к внешнему определению.

139) Например, реализация может никогда не выполнять встроенную замену или может выполнять только встроенную замену вызовов в области действия встроенного объявления.

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

static inline void f(void) {} не имеет практической разницы с static void f(void) {},

Так что да, с современными компиляторами большую часть времени нет. С любыми компиляторами нет никаких практических / наблюдаемых различий в выходе

inline void f(void) {} в C не работает как C++. Как это работает в C?

Функция, встроенная в любом месте, должна быть встроенной везде в C++, и компоновщик не должен выдавать ошибку множественного определения (определение должно быть одинаковым).

Что на самом деле делает extern inline void f(void); делать?

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

Функция, в которой все объявления (включая определение) упоминаются как встроенные, а не внешние.
Там должно быть определение в той же единице перевода. Стандарт ссылается на это как на встроенное определение.
Автономный объектный код не выдается, поэтому это определение нельзя вызвать из другого модуля перевода.

В этом примере все объявления и определения используют inline, но не extern:

// a declaration mentioning inline     
inline int max(int a, int b);

// a definition mentioning inline  
inline int max(int a, int b) {  
  return a > b ? a : b;  
}

Вот ссылка, которая может дать вам больше ясности о встроенных функциях в C, а также об использовании inline & extern.

Если вы поймете, откуда они берутся, вы поймете, почему они здесь.

И «inline», и «const» — это новшества C++, которые в конечном итоге были переделаны в C. Одной из целей дизайна, заложенных в этих нововведениях, а также в более поздних нововведениях, таких как шаблоны и даже лямбда-выражения, было выделение наиболее распространенных вариантов использования. для препроцессора (в частности, «#define»), чтобы свести к минимуму использование и потребность в фазе препроцессора.

Наличие фазы препроцессора в языке серьезно ограничивает возможность обеспечения прозрачности при анализе и переводе с языка. Это превратило то, что должно было быть простым переводом сценариев оболочки, в более сложные программы, такие как «f2c» (с Fortran на C) и исходный компилятор C++ «cfront» (с C++ на C); и, в меньшей степени, утилита «отступ». Если вам когда-либо приходилось иметь дело с выводом перевода таких преобразователей (а у нас есть) или с созданием собственных переводчиков, то вы знаете, насколько это серьезная проблема.

Утилита "indent", между прочим, возражает против всей проблемы и просто подпирает ее, компрометируя, просто рассматривая вызовы макросов как обычные переменные или вызовы функций и пропуская "#include". Проблема также возникнет с другими инструментами, которые могут захотеть выполнить преобразование/перевод источника в источник, такими как автоматизированные инструменты реинжиниринга, перекодирования и рефакторинга; то есть вещи, которые более разумно автоматизируют то, что вы, программист, делаете.

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

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

Один из распространенных вариантов использования #define для создания констант манифеста. В значительной степени теперь это можно обработать с помощью ключевого слова «const» и (в C++) «constexpr».

Еще один распространенный вариант использования «#define» — создание функций с помощью макросов. Многое из этого теперь инкапсулировано "встроенной" функцией, и это то, что она призвана заменить. Конструкция «лямбда» в C++ делает еще один шаг вперед.

И «const», и «inline» присутствовали в C++ со времени его первого внешнего выпуска — выпуска E в феврале 1985 года. распечатка нескольких сотен страниц.)

Позднее были добавлены другие новшества, такие как «шаблон» в версии 3.0 cfront (который был принят на собрании ANSI X3J16 в 1990 году), а лямбда-конструкция и «constexpr» появились совсем недавно.

Встроенный C отличается от встроенного C++.

Inline - это указание компилятору встроить функцию, где это возможно, и независимо от того, имеет ли место встраивание (на самом деле inline никогда не встраивает функцию в -O0, но всегда встраивает в -Ofast в блоке перевода), это обеспечивает следующие гарантии:

  • C99 inline / GNU89 extern inline: для этого встроенного определения не создается внешне видимая функция, но она может потребоваться, поэтому она должна существовать. Предоставляется только встроенное определение, которое компилятор будет использовать в качестве переопределения, когда решит встроить функцию. Это позволяет определять встроенное определение и автономную функцию одного и того же символа по отдельности, одно со встроенным, а другое вне строки, но не в одной и той же единице перевода. Встроенные определения всегда видны компилятору только локально, и каждая единица перевода может иметь свою собственную. Встроенные определения нельзя экспортировать в другие файлы, так как встроенные определения не достигают стадии связывания. Чтобы достичь этого во время компиляции, встроенное определение может быть в файле заголовка и включено в каждую единицу перевода.Это означает, что использование inline является директивой компилятора, а extern/static относится к автономной версии, созданной для компоновщика.
  • C99 extern inline / GNU89 inline: внешне видимая функция испускается для этого встроенного определения, что означает, что этот спецификатор может использоваться только в одной из единиц перевода, а остальные должны иметь внешние внестрочные определения. Это интуитивно противоположность extern.
  • C99 / GNU89 static inline: локально видимая внешняя функция создается для компоновщика для этого встроенного определения компилятора. Нестатическая встроенная функция не должна содержать неконстантные статические переменные продолжительности хранения или обращаться к статическим переменным области видимости файла, это приведет к предупреждению компилятора. Это связано с тем, что встроенная и автономная версии функции будут иметь разные статические переменные, если автономная версия предоставляется из другой единицы перевода. Таким образом, он напоминает программисту, что по логике он должен быть константой, потому что изменение и чтение статики приведет к неопределенному поведению; если компилятор встраивает функцию, он будет читать новое статическое значение, а не то, в которое было записано в предыдущем внешнем вызове. Если сделать его статическим, это гарантирует, что определение вне линии будет предоставлено не извне, а из встроенного определения,немного похоже на обычное определение функции, за исключением того, что другие единицы перевода не могут использовать определение функции. Предположительно, предупреждение о доступе к статическим переменным области видимости файла возникает из-за того, что встроенный будет получать доступ к определению, отличному от определения вне очереди в другом файле (если они оба включают встроенное определение, а затемextern const char *saddr(void);используется в другой единице перевода; либо символ не будет найден, необходимо также объявить отдельную статику в этом файле или использовать другое определение extern. int x; разрешен доступ, потому что он будет связан со ссылкой в ​​автономной функции в другом файле. Если функция не определена в блоке перевода, она не может быть встроена, потому что она оставлена ​​на усмотрение компоновщика. Если функция определена, но не встроена, то компилятор будет использовать эту версию, если решит встроить
  • Используя inline / extern inlineпрототип до / после не встроенного определения переопределяет прототип, как если бы он не существовал; Встроенный прототип идентичен обычному прототипу. использование встроенного прототипа перед встроенным определением - это способ создания прототипа встроенной функции без побочных эффектов; после этого он бесполезен, как обычный прототип, и будет проигнорирован.
  • Используя extern inline / extern/ обычный прототип до / после встроенного определения идентичен встроенному определению extern; он обеспечивает внешнее внешнее определение функции с использованием встроенного определения.
  • __attribute__((always_inline))всегда вставляет символ функции в единицу перевода с этим определением. При использовании во встроенной функции внешнее определение по-прежнему не предоставляется. Его можно использовать только в определениях.
  • Обычные правила для staticприменить, а затем то же самое для прототипов, отличающихся от их определений, если определение находится в другом файле. Если файл не содержит встроенного определения, то может быть предоставлено совершенно другое внешнее определение, независимо от каких-либо прототипов или встроенных определений в других файлах.

Как слово "Inline", скажем "In" "Строка", добавление этого ключевого слова в функцию влияет на программу во время выполнения, когда программа компилируется, код, написанный внутри функции, вставляется в вызов функции, так как вызовы функции обходятся дороже, чем встроенный код, так что это оптимизирует код. Таким образом, static inline void f(void) {} ​​и static void f(void) {} ​​в этом встроенном ключевом слове имеют значение во время выполнения. Но когда функция имеет слишком много строк кода, это не повлияет на время выполнения. Если вы добавите статическую функцию перед функцией, время жизни функции будет временем жизни всей программы. И использование этой функции ограничено только этим файлом. Чтобы узнать об extern, вы можете обратиться к - Влияние ключевого слова extern на функции C

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