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

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

Пример

An в соответствии Функция может быть написана на C или C ++ так:

в соответствии пустота замена(int *м, int *п){    int tmp = *м;    *м = *п;    *п = tmp;}

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

замена(&Икс, &y);

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

int tmp = Икс;Икс = y;y = tmp;

При реализации алгоритма сортировки, выполняющего много замен, это может увеличить скорость выполнения.

Стандартная поддержка

C ++ и C99, но не его предшественники K&R C и C89, есть поддержка в соответствии функции, правда, с другой семантикой. В обоих случаях, в соответствии не вызывает встраивание; компилятор может решить не встраивать функцию вообще или только в некоторых случаях. Разные компиляторы различаются по степени сложности функции, которую им удается встроить. Основные компиляторы C ++, такие как Microsoft Visual C ++ и GCC поддерживает параметр, который позволяет компиляторам автоматически встраивать любую подходящую функцию, даже если она не помечена как в соответствии функции. Однако, просто опуская в соответствии Ключевое слово, позволяющее компилятору принимать все решения по встраиванию, невозможно, поскольку компоновщик затем будет жаловаться на повторяющиеся определения в разных единицах перевода. Это потому что в соответствии не только дает компилятору подсказку, что функция должна быть встроена, но также влияет на то, будет ли компилятор генерировать вызываемую внешнюю копию функции (см. классы хранения встроенных функций ).

Нестандартные пристройки

GNU C как часть диалекта gnu89, который он предлагает, поддерживает в соответствии как расширение C89. Однако семантика отличается от семантики C ++ и C99. armcc в режиме C90 также предлагает в соответствии как нестандартное расширение с семантикой, отличной от gnu89 и C99.

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

  • Microsoft Visual C ++: __forceinline
  • gcc или clang: __attribute __ ((always_inline)) или же __attribute __ ((__ always_inline__)), последнее из которых полезно во избежание конфликта с пользовательским макросом с именем always_inline.

Неизбирательное использование этого может привести к увеличению объема кода (раздутый исполняемый файл), минимальному увеличению производительности или ее отсутствию, а в некоторых случаях даже к снижению производительности. Более того, компилятор не может встроить функцию при любых обстоятельствах, даже когда встраивание принудительно; в этом случае и gcc, и Visual C ++ генерируют предупреждения.

Принуждение к встраиванию полезно, если

  • в соответствии не соблюдается компилятором (игнорируется анализатором затрат / выгод компилятора), и
  • встраивание приводит к необходимому увеличению производительности

Для переносимости кода можно использовать следующие директивы препроцессора:

#ifdef _MSC_VER    #define forceinline __forceinline#elif определено (__ GNUC__)    #define forceinline встроенный __attribute __ ((__ always_inline__))#elif определено (__ CLANG__)    #if __has_attribute (__ always_inline__)        #define forceinline встроенный __attribute __ ((__ always_inline__))    #еще        #define forceinline inline    #endif#еще    #define forceinline inline#endif

Классы хранения встроенных функций

статический встроенный имеет одинаковые эффекты во всех диалектах C и C ++. При необходимости он выдаст локально видимую (внешнюю копию) функцию.

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

Влияние класса хранения внешний когда применяется или не применяется к в соответствии функции различаются между диалектами C[2] и C ++.[3]

C99

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

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

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

Предотвращать недостижимый код от добавления в окончательный исполняемый файл, если все использования функции были встроены, рекомендуется[3] поместить объектные файлы всех таких файлов .c с одним внешний встроенный функция в статическая библиотека файл, обычно с дуга rcs, а затем свяжите с этой библиотекой вместо отдельных объектных файлов. Это приводит к связыванию только тех объектных файлов, которые действительно необходимы, в отличие от непосредственного связывания объектных файлов, что заставляет их всегда включать в исполняемый файл. Однако файл библиотеки должен быть указан после всех других объектных файлов в командной строке компоновщика, поскольку вызовы функций из объектных файлов, указанных после файла библиотеки, компоновщиком не учитывается. Звонки из в соответствии функции для других в соответствии функции будут разрешены компоновщиком автоматически ( s вариант в дуга rcs обеспечивает это).

Альтернативное решение - использовать оптимизацию времени компоновки вместо библиотеки. gcc предоставляет флаг -Wl, - gc-разделы исключить разделы, в которых все функции не используются. Это будет иметь место для объектных файлов, содержащих код одного неиспользуемого внешний встроенный функция. Однако он также удаляет любые неиспользуемые разделы из всех других объектных файлов, а не только те, которые связаны с неиспользуемыми. внешний встроенный функции. (Может быть желательно связать в исполняемый файл функции, которые должны вызываться программистом из отладчика, а не самой программой, например, для проверки внутреннего состояния программы.) При таком подходе также возможно использовать один файл .c со всеми внешний встроенный функций вместо одного файла .c для каждой функции. Затем файл должен быть скомпилирован с -fdata-разделы -ffunction-разделы. Однако страница руководства gcc предупреждает об этом, говоря: «Используйте эти параметры только тогда, когда это дает существенные преимущества».

Некоторые рекомендуют совершенно другой подход, который заключается в определении функций как статический встроенный вместо в соответствии в файлах заголовков.[2] Тогда не будет генерироваться недостижимый код. Однако у этого подхода есть недостаток в противоположном случае: будет сгенерирован дублирующийся код, если функция не может быть встроена более чем в одну единицу трансляции. Созданный код функции не может использоваться совместно с единицами трансляции, потому что он должен иметь разные адреса. Это еще один недостаток; взяв адрес такой функции, определенной как статический встроенный в файле заголовка даст разные значения в разных единицах перевода. Следовательно, статический встроенный функции следует использовать только в том случае, если они используются только в одной единице перевода, что означает, что они должны переходить только в соответствующий файл .c, а не в файл заголовка.

gnu89

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

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

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

C ++

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

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

armcc

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

Ограничения

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

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

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

  1. они есть вариативный,
  2. использовать alloca
  3. использовать вычисленный идти к
  4. использовать нелокальный идти к
  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 модификатор.

Проблемы

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

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

Цитаты

"Объявление функции [...] с в соответствии спецификатор объявляет встроенную функцию. В в соответствии Спецификатор указывает реализации, что встроенная подстановка тела функции в точке вызова предпочтительнее обычного механизма вызова функции. Реализация не требуется для выполнения этой встроенной замены в точке вызова; однако, даже если эта встроенная подстановка опущена, все же должны соблюдаться другие правила для встроенных функций, определенные в 7.1.2. "
- ISO / IEC 14882: 2011, текущий стандарт C ++, раздел 7.1.2
"Функция, объявленная с в соответствии Спецификатор функции - это встроенная функция. [. . . ] Создание встроенной функции предполагает, что вызовы функции должны быть как можно быстрее. Степень эффективности таких предложений определяется реализацией (сноска: Например, реализация может никогда не выполнять встроенную замену или может выполнять только встроенные замены для вызовов в области встроенного объявления.)
"[...] Встроенное определение не предоставляет внешнего определения для функции и не запрещает внешнее определение в другом единица перевода. Встроенное определение предоставляет альтернативу внешнему определению, которое переводчик может использовать для реализации любого вызова функции в той же единице перевода. Не указано, использует ли вызов функции встроенное определение или внешнее определение ".
- ISO 9899: 1999 (E), стандарт C99, раздел 6.7.4

Смотрите также

Рекомендации

  1. ^ а б Мейерс, Рэнди (1 июля 2002 г.). «Новый C: встроенные функции». Цитировать журнал требует | журнал = (помощь)
  2. ^ а б http://www.greenend.org.uk/rjk/tech/inline.html
  3. ^ а б c d https://gcc.gnu.org/onlinedocs/gcc-7.1.0/gcc/Inline.html
  4. ^ http://blahg.josefsipek.net/?p=529
  5. ^ а б https://gcc.gnu.org/gcc-5/porting_to.html
  6. ^ https://gcc.gnu.org/ml/gcc-patches/2007-02/msg00119.html
  7. ^ http://infocenter.arm.com/help/topic/com.arm.doc.faqs/ka15831.html
  8. ^ страница руководства gcc, описание -fno-common

внешняя ссылка