В языках программирования C и C++ встроенная функция — это функция, определенная ключевым словом ; это служит двум целям: inline
register
класса хранения , который аналогичным образом предоставляет подсказку по оптимизации. [1]inline
— изменить поведение связывания; детали этого сложны. Это необходимо из-за модели C/C++ «раздельная компиляция + связывание», в частности, потому что определение (тело) функции должно быть продублировано во всех единицах трансляции , где оно используется, чтобы разрешить встраивание во время компиляции , что, если функция имеет внешнее связывание , вызывает коллизию во время связывания (это нарушает уникальность внешних символов). C и C++ (и диалекты, такие как GNU C и Visual C++) решают это по-разному. [1]Функцию inline
можно записать на C или C++ следующим образом:
inline void swap ( int * m , int * n ) { int tmp = * m ; * m = * n ; * n = tmp ; }
Затем следует заявление, подобное следующему:
поменять местами ( & x , & y );
может быть преобразовано в (если компилятор решит выполнить встраивание, что обычно требует включения оптимизации):
int tmp = x ; x = y ; y = tmp ;
При реализации алгоритма сортировки, выполняющего множество перестановок, это может увеличить скорость выполнения.
C++ и C99 , но не его предшественники K&R C и C89 , поддерживают inline
функции, хотя и с разной семантикой. В обоих случаях inline
не принудительно встраивает; компилятор может вообще не встраивать функцию или только в некоторых случаях. Разные компиляторы различаются по сложности функции, которую они могут встроить. Основные компиляторы C++, такие как Microsoft Visual C++ и GCC, поддерживают опцию, которая позволяет компиляторам автоматически встраивать любую подходящую функцию, даже те, которые не помечены как inline
функции. Однако просто опустить inline
ключевое слово, чтобы позволить компилятору принять все решения о встраивании, невозможно, так как тогда компоновщик будет жаловаться на дублирующиеся определения в разных единицах трансляции. Это связано inline
не только с тем, что дает компилятору подсказку о том, что функция должна быть встроена, но и влияет на то, будет ли компилятор генерировать вызываемую внешнюю копию функции (см. классы хранения встроенных функций).
GNU C , как часть диалекта gnu89, который он предлагает, имеет поддержку inline
как расширение C89. Однако семантика отличается как от C++, так и от C99. armcc в режиме C90 также предлагает inline
как нестандартное расширение с семантикой, отличной от gnu89 и C99.
Некоторые реализации предоставляют средства, с помощью которых можно заставить компилятор встроить функцию, обычно с помощью спецификаторов объявлений, специфичных для реализации:
__forceinline
__attribute__((always_inline))
или __attribute__((__always_inline__))
, последний из которых полезен для избежания конфликта с определенным пользователем макросом с именем always_inline
.Неразборчивое использование этого может привести к увеличению размера кода (раздутый исполняемый файл), минимальному или отсутствующему приросту производительности, а в некоторых случаях даже к потере производительности. Более того, компилятор не может встроить функцию при любых обстоятельствах, даже если встраивание принудительно; в этом случае и gcc, и Visual C++ генерируют предупреждения.
Принудительное встраивание полезно, если:
inline
не учитывается компилятором (игнорируется анализатором затрат/выгод компилятора)Для переносимости кода можно использовать следующие директивы препроцессора:
#ifdef _MSC_VER #define forceinline __forceinline #elif defined(__GNUC__) #define forceinline inline __attribute__((__always_inline__)) #elif defined(__CLANG__) #if __has_attribute(__always_inline__) #define forceinline inline __attribute__((__always_inline__)) #else #define forceinline inline #endif #else #define forceinline inline #endif
static inline
имеет те же эффекты во всех диалектах C и C++. Он выдаст локально видимую (внешнюю копию) функцию, если это необходимо.
Независимо от класса хранения компилятор может игнорировать inline
квалификатор и генерировать вызов функции во всех диалектах C и C++.
Эффект класса хранения extern
, применяемый или не применяемый к inline
функциям, различается в диалектах C [2] и C++. [3]
В C99 определенная функция inline
никогда не будет, а определенная функция extern inline
всегда будет, испускать внешне видимую функцию. В отличие от C++, нет способа запросить внешне видимую функцию, совместно используемую единицами перевода, чтобы она испускалась только при необходимости.
Если inline
объявления смешаны с extern inline
объявлениями или с неквалифицированными объявлениями (т. е. без inline
квалификатора или класса хранения), единица трансляции должна содержать определение (независимо от того, является ли оно неквалифицированным, inline
, или extern inline
), и для него будет создана внешне видимая функция.
Определенная функция inline
требует ровно одну функцию с таким именем где-то еще в программе, которая либо определена extern inline
, либо не имеет квалификатора. Если во всей программе указано более одного такого определения, компоновщик будет жаловаться на дублирующиеся символы. Однако, если его не хватает, компоновщик не обязательно будет жаловаться, потому что, если бы все использования могли быть вставлены, он не нужен. Но он может жаловаться, так как компилятор всегда может игнорировать квалификатор inline
и вместо этого генерировать вызовы функции, как это обычно происходит, если код компилируется без оптимизации. (Это может быть желаемым поведением, если функция должна быть вставлена везде всеми способами, и должна быть сгенерирована ошибка, если это не так.) Удобный способ — определить функции inline
в заголовочных файлах и создать один файл .c для каждой функции, содержащий extern inline
объявление для нее и включающий соответствующий заголовочный файл с определением. Неважно, находится ли объявление до или после включения.
Чтобы предотвратить добавление недостижимого кода в конечный исполняемый файл, если все использования функции были встроены, рекомендуется [3] поместить объектные файлы всех таких .c-файлов с одной extern inline
функцией в статический файл библиотеки , обычно с помощью ar rcs
, а затем выполнить компоновку с этой библиотекой вместо отдельных объектных файлов. Это приводит к тому, что будут связаны только те объектные файлы, которые действительно нужны, в отличие от прямого связывания объектных файлов, которое приводит к тому, что они всегда включаются в исполняемый файл. Однако файл библиотеки должен быть указан после всех других объектных файлов в командной строке компоновщика, поскольку вызовы из объектных файлов, указанных после файла библиотеки, к функциям не будут рассматриваться компоновщиком. Вызовы из inline
функций к другим inline
функциям будут разрешены компоновщиком автоматически ( s
опция in ar rcs
обеспечивает это).
Альтернативным решением является использование оптимизации времени компоновки вместо библиотеки. gcc предоставляет флаг -Wl,--gc-sections
для пропуска разделов, в которых все функции не используются. Это будет иметь место для объектных файлов, содержащих код одной неиспользуемой extern inline
функции. Однако он также удаляет любые и все другие неиспользуемые разделы из всех других объектных файлов, а не только те, которые связаны с неиспользуемыми extern inline
функциями. (Может потребоваться связать с исполняемым файлом функции, которые должны вызываться программистом из отладчика, а не самой программой, например, для проверки внутреннего состояния программы.) При таком подходе также можно использовать один файл .c со всеми extern inline
функциями вместо одного файла .c на функцию. Тогда файл должен быть скомпилирован с помощью -fdata-sections -ffunction-sections
. Однако страница руководства gcc предупреждает об этом, говоря: «Используйте эти параметры только в том случае, если от этого есть существенные преимущества».
Некоторые рекомендуют совершенно другой подход, который заключается в определении функций как , static inline
а не inline
в заголовочных файлах. [2] Тогда не будет сгенерирован недостижимый код. Однако этот подход имеет недостаток в противоположном случае: будет сгенерирован дублирующий код, если функция не может быть встроена более чем в одну единицу трансляции. Выданный код функции не может быть общим для единиц трансляции, поскольку он должен иметь разные адреса. Это еще один недостаток; взятие адреса такой функции, определенной как static inline
в заголовочном файле, даст разные значения в разных единицах трансляции. Поэтому static inline
функции следует использовать только в том случае, если они используются только в одной единице трансляции, что означает, что они должны идти только в соответствующий файл .c, а не в заголовочный файл.
Семантика inline
и в gnu89 extern inline
по сути является полной противоположностью семантике в C99, [4] за исключением того, что gnu89 допускает переопределение функции extern inline
как неквалифицированной функции, в то время как в C99 inline
этого нет. [5] Таким образом, gnu89 extern inline
без переопределения похож на C99 inline
, а gnu89 inline
похож на C99 extern inline
; другими словами, в gnu89 определенная функция inline
всегда будет и определенная функция extern inline
никогда не будет генерировать внешне видимую функцию. Обоснованием этого является то, что она сопоставляет переменные, для которых никогда не будет зарезервирована память, если они определены как extern
и всегда, если они определены без. Обоснованием C99, напротив, является то, что было бы удивительно, если бы использование inline
имело побочный эффект — всегда генерировать невстроенную версию функции — что противоречит тому, что предполагает его название.
Замечания для C99 о необходимости предоставления ровно одного внешне видимого экземпляра функции для встроенных функций и о возникающей в результате проблеме с недостижимым кодом применимы mutatis mutandis и к gnu89.
gcc до версии 4.2 включительно использовал inline
семантику gnu89 даже тогда, когда -std=c99
было явно указано. [6] С версией 5 [5] gcc перешел с диалекта gnu89 на диалект gnu11, фактически включив inline
семантику C99 по умолчанию. Чтобы использовать семантику gnu89 вместо этого, ее необходимо включить явно, либо с помощью -std=gnu89
или , чтобы повлиять только на встраивание, -fgnu89-inline
, либо добавив gnu_inline
атрибут ко всем inline
объявлениям. Чтобы гарантировать семантику C99, можно использовать либо -std=c99
, -std=c11
, -std=gnu99
или -std=gnu11
(без ). [3]-fgnu89-inline
В C++ определенная функция inline
, если требуется, выдаст функцию, совместно используемую единицами трансляции, обычно путем помещения ее в общий раздел объектного файла, для которого она нужна. Функция должна иметь одинаковое определение везде, всегда с квалификатором inline
. В C++ extern inline
то же самое, что и inline
. Обоснованием подхода C++ является то, что это наиболее удобный способ для программиста, поскольку не нужно предпринимать никаких специальных мер предосторожности для устранения недостижимого кода и, как и для обычных функций, не имеет значения, extern
указано или нет.
Квалификатор inline
автоматически добавляется к функции, определенной как часть определения класса.
armcc в режиме C90 предоставляет extern inline
и inline
семантику, которые такие же, как в C++: такие определения будут генерировать функцию, совместно используемую единицами трансляции, если это необходимо. В режиме C99 extern inline
всегда генерирует функцию, но, как и в C++, она будет совместно используемой единицами трансляции. Таким образом, одна и та же функция может быть определена extern inline
в разных единицах трансляции. [7] Это соответствует традиционному поведению компиляторов Unix C [8] для множественных extern
неопределений неинициализированных глобальных переменных.
Получение адреса функции inline
требует кода для невстроенной копии этой функции, которая будет выдана в любом случае.
В C99 функция inline
или extern inline
не должна обращаться к static
глобальным переменным или определять нелокальные const
static
переменные. const static
Локальные переменные могут быть или не быть разными объектами в разных единицах перевода, в зависимости от того, была ли функция встроена или был сделан вызов. Только static inline
определения могут ссылаться на идентификаторы с внутренней связью без ограничений; это будут разные объекты в каждой единице перевода. В C++ разрешены как const
и нелокальные const
static
, и они ссылаются на один и тот же объект во всех единицах перевода.
gcc не может встраивать функции, если [3]
alloca
goto
goto
setjmp
__builtin_longjmp
__builtin_return
или__builtin_apply_args
Согласно спецификациям Microsoft на сайте MSDN, MS Visual C++ не может быть встроен (даже с помощью __forceinline
), если
#pragma inline_recursion(on)
. С помощью прагмы рекурсивные функции встраиваются на глубину по умолчанию в 16 вызовов. Чтобы уменьшить глубину встраивания, используйте inline_depth
прагму.__declspec
модификатором naked.Помимо проблем с расширением встроенных функций в целом (см. Расширение встроенных функций § Влияние на производительность ), inline
функции как языковая возможность могут быть не столь ценны, как кажутся, по ряду причин:
inline
) становится доступен ее клиенту (вызывающей функции).inline
в C99 требует ровно одного внешнего определения функции, если она где-то используется. Если такое определение не было предоставлено программистом, это может легко привести к ошибкам компоновщика. Это может произойти при отключенной оптимизации, что обычно предотвращает встраивание. С другой стороны, добавление определений может привести к недостижимому коду, если программист не избежит этого тщательно, поместив их в библиотеку для компоновки, используя оптимизацию времени компоновки или static inline
.inline
функцию в каждом модуле (единице трансляции), который ее использует, тогда как обычная функция должна быть определена только в одном модуле. В противном случае было бы невозможно скомпилировать один модуль независимо от всех других модулей. В зависимости от компилятора это может привести к тому, что каждый соответствующий объектный файл будет содержать копию кода функции для каждого модуля с некоторым использованием, которое не может быть встроено.Объявление функции ... со спецификатором inline объявляет встроенную функцию. Спецификатор inline указывает реализации, что встроенная подстановка тела функции в точке вызова предпочтительнее обычного механизма вызова функции. Реализация не обязана выполнять эту встроенную подстановку в точке вызова; однако, даже если эта встроенная подстановка опущена, другие правила для встроенных функций, определенные в 7.1.2, все равно должны соблюдаться.
— ISO/IEC 14882:2011, текущий стандарт C++, раздел 7.1.2
Функция, объявленная с помощью спецификатора встроенной функции, является встроенной функцией ... Создание функции как встроенной функции предполагает, что вызовы функции должны быть максимально быстрыми. Степень эффективности таких предложений определяется реализацией ( сноска: например, реализация может никогда не выполнять встроенную замену или может выполнять встроенные замены только для вызовов в области действия встроенного объявления. )
... Встроенное определение не предоставляет внешнего определения для функции и не запрещает внешнее определение в другой единице перевода . Встроенное определение предоставляет альтернативу внешнему определению, которую переводчик может использовать для реализации любого вызова функции в той же единице перевода. Не указано, использует ли вызов функции встроенное определение или внешнее определение.
— ISO 9899:1999(E), стандарт C99, раздел 6.7.4
{{cite journal}}
: Цитировать журнал требует |journal=
( помощь )-fno-common