Jump to content

Встроенная функция

В C и C++ языках программирования определяется встроенная функция ключевым словом inline; это служит двум целям:

  1. Она служит директивой компилятора , которая предлагает (но не требует), чтобы компилятор подставил тело функции в строке, выполняя встроенное расширение , т. е. вставляя код функции по адресу каждого вызова функции, тем самым экономя накладные расходы функции. вызов. В этом отношении он аналогичен register Спецификатор класса хранения , который также предоставляет подсказку по оптимизации. [1]
  2. Вторая цель inline заключается в изменении поведения связей; детали этого сложны. Это необходимо из-за отдельной модели компиляции + компоновки C/C++, в частности потому, что определение (тело) функции должно дублироваться во всех единицах трансляции , где оно используется, чтобы разрешить встраивание во время компиляции , что, если функция имеет внешние linkage вызывает коллизию при связывании (нарушает уникальность внешних символов). C и C++ (и такие диалекты, как GNU C и Visual C++) решают эту проблему по-разному. [1]

Ан inline функцию можно записать на C или C++ следующим образом:

inline void swap(int *m, int *n)
{
    int tmp = *m;
    *m = *n;
    *n = tmp;
}

Затем заявление, подобное следующему:

swap(&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.

Некоторые реализации предоставляют средства, с помощью которых компилятор может встроить функцию, обычно с помощью спецификаторов объявлений, специфичных для конкретной реализации:

  • Microsoft Visual С++: __forceinline
  • gcc или clang: __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] и С++. [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 вариант в 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, а не к файлу заголовка.

семантика gnu89 inline и extern inline по сути являются полной противоположностью тем, что были в C99, [4] за исключением того, что gnu89 позволяет переопределить extern inline функционировать как неполная функция, а C99 inline нет. [5] Таким образом, gnu89 extern inline без переопределения как С99 inlineи gnu89 inline это как C99 extern inline; другими словами, в gnu89 функция, определенная inline всегда будет и функция определена extern inline никогда не будет испускать внешне видимую функцию. Обоснованием этого является то, что он соответствует переменным, для которых память никогда не будет зарезервирована, если они определены как extern и всегда, если определено без. Обоснование C99, напротив, заключается в том, что было бы удивительно, если бы inline будет иметь побочный эффект — всегда выдавать невстроенную версию функции — что противоречит тому, что предполагает ее название.

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

gcc до версии 4.2 включительно использовал gnu89 inline семантика, даже когда -std=c99 было указано явно. [6] С версией 5, [5] gcc переключился с gnu89 на диалект gnu11, что фактически позволило использовать C99. inline семантика по умолчанию. Чтобы вместо этого использовать семантику gnu89, их необходимо включить явно, либо с помощью -std=gnu89 или, чтобы повлиять только на встраивание, -fgnu89-inlineили добавив gnu_inline отнести ко всем inline декларации. Чтобы обеспечить семантику C99, либо -std=c99, -std=c11, -std=gnu99 или -std=gnu11 (без -fgnu89-inline) можно использовать. [3]

В C++ функция, определенная inline при необходимости выдаст функцию, разделяемую между единицами перевода, обычно помещая ее в общий раздел объектного файла, для которого она необходима. Функция должна иметь одно и то же определение везде, всегда с inline квалификатор. В С++ extern inline то же самое, что inline. Обоснование подхода C++ состоит в том, что это наиболее удобный способ для программиста, поскольку не нужно предпринимать никаких особых мер предосторожности для устранения недостижимого кода и, как и в случае с обычными функциями, не имеет значения, будет ли extern указано или нет.

The inline квалификатор автоматически добавляется к функции, определенной как часть определения класса.

Armcc в режиме C90 обеспечивает extern inline и inline семантика такая же, как в C++: такие определения при необходимости будут генерировать функцию, разделяемую между единицами перевода. В режиме C99 extern inline всегда генерирует функцию, но, как и в C++, она будет совместно использоваться несколькими единицами перевода. Таким образом, одну и ту же функцию можно определить extern inline в разных единицах перевода. [7] Это соответствует традиционному поведению компиляторов C Unix. [8] для нескольких не- extern определения неинициализированных глобальных переменных.

Ограничения

[ редактировать ]

Взяв адрес inline функция требует кода для создания невстроенной копии этой функции в любом случае.

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

gcc не может встраивать функции, если [3]

  1. они вариативные ,
  2. использовать alloca
  3. использовать вычисленный goto
  4. используйте нелокальный goto
  5. использовать вложенные функции
  6. использовать setjmp
  7. использовать __builtin_longjmp
  8. использовать __builtin_return, или
  9. использовать __builtin_apply_args

Согласно спецификациям Microsoft в MSDN, MS Visual C++ не может быть встроен (даже с __forceinline), если

  1. Функция или ее вызывающая сторона компилируется с параметром /Ob0 (параметр по умолчанию для отладочных сборок).
  2. Функция и вызывающий объект используют разные типы обработки исключений (обработка исключений C++ в одном, структурированная обработка исключений в другом).
  3. Функция имеет переменный список аргументов .
  4. Функция использует встроенный ассемблер , если она не скомпилирована с параметрами /Og, /Ox, /O1 или /O2.
  5. Функция рекурсивная и не сопровождается #pragma inline_recursion(on). С помощью прагмы рекурсивные функции встроены до глубины вызовов по умолчанию, равной 16. Чтобы уменьшить глубину встраивания, используйте inline_depth прагма.
  6. Функция виртуальная и вызывается виртуально. Прямые вызовы виртуальных функций могут быть встроены.
  7. Программа принимает адрес функции и вызов осуществляется через указатель на функцию. Прямые вызовы функций, адреса которых были взяты, могут быть встроены.
  8. Функция также отмечена голым __declspec модификатор.

Проблемы

[ редактировать ]

Помимо проблем со встроенным расширением в целом (см. Встроенное расширение § Влияние на производительность ), inline Функции как особенность языка могут быть не такими ценными, как кажутся, по ряду причин:

  • Зачастую компилятор может лучше, чем человек, решить, следует ли встраивать ту или иную функцию. Иногда компилятор не может встроить столько функций, сколько указывает программист.
  • Важным моментом, который следует отметить, является то, что код (из inline функция) становится доступной своему клиенту (вызывающей функции).
  • По мере развития функций они могут стать пригодными для встраивания там, где они не были раньше, или перестать подходить для встраивания там, где они были раньше. Хотя встроить или отменить встраивание функции проще, чем преобразование в макрос и обратно, оно все равно требует дополнительного обслуживания, которое обычно дает относительно небольшую выгоду.
  • Встроенные функции, широко используемые в собственных системах компиляции на основе C, могут увеличить время компиляции, поскольку промежуточное представление их тел копируется в каждый сайт вызова.
  • Спецификация inline в C99 требуется ровно одно внешнее определение функции, если она где-то используется. Если такое определение не было предоставлено программистом, это может легко привести к ошибкам компоновщика. Это может произойти, если оптимизация отключена, что обычно предотвращает встраивание. С другой стороны, добавление определений может привести к недоступности кода, если программист не избежит этого тщательно, поместив их в библиотеку для компоновки, используя оптимизацию времени компоновки или static inline.
  • В C++ необходимо определить inline функция в каждом модуле (единице перевода), который ее использует, тогда как обычная функция должна быть определена только в одном модуле. В противном случае было бы невозможно скомпилировать один модуль независимо от всех других модулей. В зависимости от компилятора это может привести к тому, что каждый соответствующий объектный файл будет содержать копию кода функции для каждого модуля, который не может быть встроен.
  • Во встроенном программном обеспечении часто определенные функции необходимо поместить в определенные разделы кода с помощью специальных инструкций компилятора, таких как операторы «прагмы». Иногда функции в одном сегменте памяти может потребоваться вызвать функцию в другом сегменте памяти, и если происходит встраивание вызываемой функции, то код вызываемой функции может оказаться в сегменте, где его быть не должно. Например, сегменты высокопроизводительной памяти могут быть очень ограничены в пространстве кода, и если функция, принадлежащая такому пространству, вызывает другую большую функцию, которая не должна находиться в разделе высокой производительности, и вызванная функция неправильно встраивается, то это может привести к нехватке места для кода в высокопроизводительном сегменте памяти. По этой причине иногда необходимо убедиться, что функции не становятся встроенными.

Объявление функции   ... со встроенным спецификатором объявляет встроенную функцию. Спецификатор inline указывает реализации, что встроенная замена тела функции в точке вызова должна быть предпочтительнее обычного механизма вызова функции. Реализация не требуется для выполнения этой встроенной замены в точке вызова; однако, даже если эта встроенная замена опущена, другие правила для встроенных функций, определенные в 7.1.2, все равно должны соблюдаться.

ISO/IEC 14882:2011, текущий стандарт C++, раздел 7.1.2.

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

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

ISO 9899:1999(E), стандарт C99, раздел 6.7.4.

См. также

[ редактировать ]
  1. ^ Jump up to: а б Мейерс, Рэнди (1 июля 2002 г.). «Новый C: встроенные функции» . {{cite journal}}: Для цитирования журнала требуется |journal= ( помощь )
  2. ^ Jump up to: а б «Встроенные функции в C» .
  3. ^ Jump up to: а б с д «Использование коллекции компиляторов GNU (GCC): встроенный» .
  4. ^ «Йозеф «Джефф» Сипек » Встроенный GNU против встроенного C99» .
  5. ^ Jump up to: а б «Портирование на GCC 5 — проект GNU» .
  6. ^ «Иэн Лэнс Тейлор — Очистка встроенного внешнего интерфейса» .
  7. ^ «Документация — Arm Developer» .
  8. ^ страница руководства gcc, описание -fno-common
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: d27a72a7fa0e74d636989f22783bb4d7__1712420100
URL1:https://arc.ask3.ru/arc/aa/d2/d7/d27a72a7fa0e74d636989f22783bb4d7.html
Заголовок, (Title) документа по адресу, URL1:
Inline function - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)