stringtranslate.com

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

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

  1. Он служит в качестве директивы компилятора , которая предполагает (но не требует), чтобы компилятор заменил тело функции inline, выполнив inline expansion , т. е. вставив код функции по адресу каждого вызова функции, тем самым экономя накладные расходы на вызов функции. В этом отношении он аналогичен спецификатору register класса хранения , который аналогичным образом предоставляет подсказку по оптимизации. [1]
  2. Вторая цель 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.

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

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

Принудительное встраивание полезно, если:

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

#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]

С99

В 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, а не в заголовочный файл.

gnu89

Семантика 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

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]

  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модификатором naked.

Проблемы

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

Кавычки

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

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

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

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

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

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

Ссылки

  1. ^ ab Мейерс, Рэнди (1 июля 2002 г.). «Новый C: Встроенные функции». {{cite journal}}: Цитировать журнал требует |journal=( помощь )
  2. ^ ab "Встроенные функции в C".
  3. ^ abcd «Использование коллекции компиляторов GNU (GCC): встроенный».
  4. ^ "Джозеф "Джефф" Сипек » Встроенный GNU против встроенного C99".
  5. ^ ab "Перенос на GCC 5 - проект GNU".
  6. ^ «Иэн Лэнс Тейлор — Очистка внешних встроенных компонентов».
  7. ^ «Документация – Разработчик Arm».
  8. ^ Страница руководства gcc, описание-fno-common

Внешние ссылки