Перейти к содержимому

Inline c что это

  • автор:

Встроенные функции (C++)

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

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

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

Функция, определенная в тексте объявления класса, неявно является встроенной функцией.

Пример

В следующем объявлении класса конструктор является встроенной функцией, Account так как она определена в тексте объявления класса. Функции-члены GetBalance Deposit и Withdraw указаны inline в их определениях. Ключевое слово inline является необязательным в объявлениях функций в объявлении класса.

// Inline_Member_Functions.cpp class Account < public: Account(double initial_balance) < balance = initial_balance; >double GetBalance(); double Deposit( double Amount ); double Withdraw( double Amount ); private: double balance; >; inline double Account::GetBalance() < return balance; >inline double Account::Deposit( double Amount ) < return ( balance += Amount ); >inline double Account::Withdraw( double Amount ) < return ( balance -= Amount ); >int main()

В объявлении класса функции объявлялись без inline ключевое слово. Ключевое слово inline можно указать в объявлении класса. Результат совпадает.

Заданная встроенная функция-член должна быть объявлена одинаково в каждом блоке компиляции. Должно быть ровно одно определение встроенной функции.

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

inline , __inline и __forceinline

__inline И inline описатели предлагают компилятору, что он вставляет копию текста функции в каждое место, где вызывается функция.

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

Ключевое слово __forceinline переопределяет анализ затрат и полагается на решение программиста. Соблюдайте осторожность при использовании __forceinline . Неизбирательное использование __forceinline может привести к увеличению объема кода только с незначительными повышениями производительности или, в некоторых случаях, даже с потерей производительности (из-за увеличения разбиения по страницам большего исполняемого файла, например).

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

Для совместимости с предыдущими версиями _inline и _forceinline являются синонимами __inline и __forceinline , соответственно, если не указан параметр компилятора /Za (отключение расширений языка).

Ключевое слово inline сообщает компилятору, что встроенное расширение предпочтительнее. Однако компилятор может игнорировать его. Два случая, когда такое поведение может произойти:

  • Рекурсивные функции.
  • Функции, на которые создаются ссылки посредством указателя в любом месте блока трансляции.

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

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

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

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

Пример 1

// inline_keyword1.cpp // compile with: /c inline int max( int a , int b ) < if( a >b ) return a; return b; > 

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

Пример 2

// inline_keyword2.cpp // compile with: /EHsc /c #include using namespace std; class MyClass < public: void print() < cout // Implicitly inline private: int i; >; 

Только для систем Майкрософт

Ключевое слово __inline эквивалентен inline .

Даже если __forceinline компилятор не может встраить функцию, если:

  • Функция или вызывающий объект компилируется с /Ob0 помощью (параметр по умолчанию для отладочных сборок).
  • В функции и вызывающем объекте используются разные типы (в одном — обработка исключений C++, а в другом — структурированная).
  • Функция имеет переменное число аргументов.
  • Функция использует встроенную сборку, если только не компилируется с /Ox , /O1 или /O2 .
  • Функция рекурсивна и не имеет #pragma inline_recursion(on) набора. С помощью этой директивы выполняется подстановка рекурсивных функций с глубиной по умолчанию, 16 вызовам. Чтобы уменьшить глубину встраивание, используйте inline_depth pragma.
  • Функция является виртуальной, и для нее используется виртуальный вызов. Прямые вызовы виртуальных функций могут подставляться.
  • Программа принимает адрес функции, и вызов совершается через указатель на функцию. Прямые вызовы функций, чей адрес был принят, могут подставляться.
  • Функция также помечена модификатором naked__declspec .

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

  • Функция компилируется с помощью /Od или /Ob0. В этих случаях в этих случаях не ожидается встраивание.
  • Функция определяется внешне в включенной библиотеке или другом модуле перевода или является целевым объектом виртуального вызова или целевым объектом косвенного вызова. Компилятор не может определить нелинейный код, который он не может найти в текущем модуле перевода.

Рекурсивные функции можно заменить встроенным кодом на глубину, указанную inline_depth pragma, до 16 вызовов. Начиная с этой глубины рекурсивные функции обрабатываются как вызовы на экземпляр функции. Глубина, к которой рекурсивные функции проверяются встроенной эвристики, не может превышать 16. inline_recursion Pragma управляет встроенным расширением функции, в настоящее время выполняющегося в настоящее время. Дополнительные сведения см. в параметре компилятора inline-Function ( /Ob).

Завершение блока, относящегося только к системам Майкрософт

Дополнительные сведения об использовании inline описателя см. в следующих статье:

  • Встроенные функции-члены класса
  • Определение встроенных функций C++ с помощью dllexport и dllimport

Когда использовать встраиваемые функции

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

Класс Point можно определить следующим образом:

// when_to_use_inline_functions.cpp class Point < public: // Define "accessor" functions as // reference types. unsigned& x(); unsigned& y(); private: unsigned _x; unsigned _y; >; inline unsigned& Point::x() < return _x; >inline unsigned& Point::y() < return _y; >int main()

Предполагая, что манипуляция координатами является относительно распространенной операцией в клиенте такого класса, указывая две функции доступа ( x и y в предыдущем примере), как inline правило, экономит затраты на:

  • вызовы функций (включая передачу параметров и размещение адреса объекта в стеке);
  • сохранение кадра стека вызывающего объекта;
  • Настройка нового кадра стека
  • передачу возвращаемого значения;
  • Восстановление старого кадра стека
  • Возврат

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

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

#include #define mult(a, b) a * b inline int multiply(int a, int b) < return a * b; >int main() < std::cout >; // no warning std::cout 
35 100 4.4 4 

Ниже приведены некоторые различия между макросом и встроенной функцией:

  • Макросы всегда развертываются встроенными. Однако встроенная функция встраивается только в том случае, если компилятор определяет, что это оптимальное действие.
  • Макрос может привести к неожиданному поведению. Например, макрос mult(5+5,5+5) расширяется до 5 + 5 * 5 + 5 35, в то время как функция оценивает 10 * 10 . Вы можете решить эту проблему, определив макрос как #define mult(a, b) ((a)*(b)) .
  • Встроенная функция подвергается семантической обработке компилятором, а препроцессор расширяет макросы без того же преимущества. Макросы не являются типобезопасными, в то время как функции являются.
  • Выражения, передаваемые во встраиваемые функции в качестве аргументов, вычисляются один раз. В некоторых случаях выражения, передаваемые в макросы в качестве аргументов, можно вычислить несколько раз. Например, рассмотрим следующее:
#define sqr(a) ((a) * (a)) int main() < int c = 5; std::cout 

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

Inline функции в C и С++

Ну например в стандартном C inline функции появились только в 99 стандарте (хотя как расширения поддерживались многими компиляторами и до этого).

20 ноя 2017 в 19:12

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

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

Если рассматривать функции с внутренним связыванием, т.е. функции, объявленные как static inline , то разницы между C и С++ фактически нет (если я ничего не упускаю).

Однако как только речь заходит о функциях с внешним связыванием, то разница между языками довольна значительна.

  • В С++ правила просты: допускаются множественные определения inline функций (в разных единицах трансляции). При этом если функция объявлена inline в одной единице трансляции, то и во всех остальных единицах трансляции, где она объявлена, она должна быть объявлена именно inline . Во всех единицах трансляции, где эта функция определена, она должна быть определена одинаково.
  • В языке С же проводится довольно запутанное деление между inline-определениями функции и external-определениями функции.
    • Inline-определение возникает тогда, когда в данной единице трансляции все объявления данной функции сделаны с ключевым словом inline , но ни одно не содержит ключевого слова extern . В такой ситуации определение функции не создает внешнего символа - к нему нельзя прилинковаться из другого объектного файла. В inline-определениях запрещается определять модифицируемые статические объекты и thread-локальные объекты. Также оттуда нельзя ссылаться на сущности (объекты и функции) с внутренним связыванием.
    • External-определение возникает тогда, когда в данной единице трансляции либо есть "обычное" объявление функции (без inline ), либо объявление сразу с двумя ключевыми словами extern inline . External-определение является обычной функцией - оно порождает внешний символ к которому можно прилинковаться из другого объектного файла - достаточно сделать там объявление этой функции.
    inline void foo(); // Объявление inline void bar(); // Объявление inline void foo() // Определение < static int i = 42; >inline void bar() // Определение < static int j = 42; // Ошибка! >void foo(); // Объявление inline void bar(); // Объявление 

    В данном примере определение функции bar является ошибочным, т.к. это inline-определение, а в inline-определениях нельзя определять модифицируемые статические объекты.

    В то же время определение функции foo является external-определением потому, что ниже по тексту встречается объявление это функции без слова inline . На такое определение никаких ограничений не накладывается.

    • Если в какой-то единице трансляции наличествует inline-определение функции, и нигде в проекте нет extern-определения этой функции - то вызываться будет именно inline-определение.
    • Если в какой-то единице трансляции наличествует inline-определение функции, и где-то в проекте есть extern-определение этой функции - то компилятор имеет право сам выбрать, какое определение вызывать.

    Встраиваемые функции

    В ызов функции, хоть в си он очень быстрый, отнимает некоторое время. В современном си есть возможность объявлять встраиваемые функции. При компиляции вызов функции будет заменён её телом.

    Для объявления встраиваемой функции используется ключевое слово inline (или __inline, __forceinline в зависимости от компилятора)

    #include inline int fun (int a, int b) __attribute__((always_inline)); int main() < int result = fun(2, 3); printf("%d", result); getchar(); return 0; >inline int fun(int a, int b)

    Здесь, для тестирования, использованы атрибуты компилятора gcc, которые форсируют встраивание. Рассмотрим код, который компилируется при использовании inline

    0004016f0 : 4016f0: 55 push %ebp 4016f1: 89 e5 mov %esp,%ebp 4016f3: 83 e4 f0 and $0xfffffff0,%esp 4016f6: 83 ec 20 sub $0x20,%esp 4016f9: e8 c2 00 00 00 call 4017c0 4016fe: c7 44 24 18 02 00 00 movl $0x2,0x18(%esp) 401705: 00 401706: c7 44 24 14 03 00 00 movl $0x3,0x14(%esp) 40170d: 00 40170e: 8b 54 24 18 mov 0x18(%esp),%edx 401712: 8b 44 24 14 mov 0x14(%esp),%eax 401716: 01 d0 add %edx,%eax 401718: 89 44 24 1c mov %eax,0x1c(%esp) 40171c: 8b 44 24 1c mov 0x1c(%esp),%eax 401720: 89 44 24 04 mov %eax,0x4(%esp) 401724: c7 04 24 64 50 40 00 movl $0x405064,(%esp) 40172b: e8 a8 1f 00 00 call 4036d8 401730: e8 cb 1f 00 00 call 403700 401735: b8 00 00 00 00 mov $0x0,%eax 40173a: c9 leave 40173b: c3 ret

    И без использования (видим вызов функции CALL в строке 10)

    004016f0 : 4016f0: 55 push %ebp 4016f1: 89 e5 mov %esp,%ebp 4016f3: 83 e4 f0 and $0xfffffff0,%esp 4016f6: 83 ec 20 sub $0x20,%esp 4016f9: e8 d2 00 00 00 call 4017d0 4016fe: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 401705: 00 401706: c7 04 24 02 00 00 00 movl $0x2,(%esp) 40170d: e8 24 00 00 00 call 401736 401712: 89 44 24 1c mov %eax,0x1c(%esp) 401716: 8b 44 24 1c mov 0x1c(%esp),%eax 40171a: 89 44 24 04 mov %eax,0x4(%esp) 40171e: c7 04 24 64 50 40 00 movl $0x405064,(%esp) 401725: e8 be 1f 00 00 call 4036e8 40172a: e8 e1 1f 00 00 call 403710 40172f: b8 00 00 00 00 mov $0x0,%eax 401734: c9 leave 401735: c3 ret 00401736 : 401736: 55 push %ebp 401737: 89 e5 mov %esp,%ebp 401739: 8b 55 08 mov 0x8(%ebp),%edx 40173c: 8b 45 0c mov 0xc(%ebp),%eax 40173f: 01 d0 add %edx,%eax 401741: 5d pop %ebp 401742: c3 ret 401743: 90 nop

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

    Во-вторых, для встраиваемых систем, в которых разные функции могут располагаться в разных сегментах памяти, это недопустимо, так как вызов может произойти не в том сегменте, в котором ожидалось.

    В-третьих, это даёт достаточно малый прирост производительности, но усложняет процесс сборки, оптимизации и увеличивает время компиляции. Во время внешнего связывания (external linkage) также могут возникнуть проблемы, если функция не была объявлена inline во всех компилируемых модулях. Поэтому часто встраиваемые функции объявляют также статическими.

    ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

    email

    Всё ещё не понятно? – пиши вопросы на ящик

    Урок №101. Встроенные функции

    Использование функций имеет много преимуществ, в том числе:

    Код, находящийся внутри функции, может быть повторно использован.

    Гораздо проще изменить или обновить код в функции (что делается один раз), нежели искать и изменять все части кода в функции main() «на месте». Дублирование кода — хороший рецепт для ошибок и ухудшения производительности.

    Упрощение чтения и понимания кода, так как вам не нужно знать реализацию функции, чтобы её использовать (предполагается наличие информативного названия функции и комментариев).

    В функциях поддерживается проверка типов данных для гарантии того, что передаваемые аргументы соответствуют параметрам функции.

    Однако, одним из главных недостатков использования функций является то, что при каждом их вызове происходит расход ресурсов, что влияет на производительность программы. Это связано с тем, что ЦП должен хранить адрес текущей команды (инструкции или стейтмента), которую он выполняет (чтобы знать, куда нужно будет вернуться позже), вместе с другими данными. Затем точка выполнения перемещается в другое место программы. Дальше все параметры функции должны быть созданы и им должны быть присвоены значения. И только потом, после выполнения функции, точка выполнения возвращается обратно. Код, написанный «на месте», выполняется значительно быстрее.

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

    Язык C++ предлагает возможность совместить все преимущества функций вместе с высокой производительностью кода, написанного «на месте». Речь идет о встроенных функциях. Ключевое слово inline используется для запроса, чтобы компилятор рассматривал вашу функцию как встроенную. При компиляции вашего кода, все встроенные функции (англ. «inline functions») раскрываются «на месте», то есть вызов функции заменяется копией содержимого самой функции, и ресурсы, которые могли бы быть потрачены на вызов этой функции, сохраняются! Минусом является лишь увеличение компилируемого кода за счет того, что встроенная функция раскрывается в коде при каждом вызове (особенно если она длинная и/или её вызывают много раз). Рассмотрим следующий фрагмент кода:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *