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 функции весьма занятный рудимент, доставшийся современному миру из уже далеких бандитских 90-х. Когда процветал ассемблер, Си компилировал очень компактные и маленькие программы, когда процессоры были слабенькими (по сравнению с тем, что сейчас в мобилки ставят к примеру) и время выполнения кода ценилось на вес золота. Этот тип функций вообще-то использовался не только в Си, и в то лихое время оправдывал себя.
Я думаю, не для кого не секрет, что древние компьютеры требовали при написании ПО для тяжелых вычислений достаточно таки серьезного подхода с позиции программиста. Выкручиваться и экономить приходилось на всём, иначе время работы программы увеличивалось в разы. Это сейчас мы гоняем гигабайтные игрушки не особо жалуясь на скорость работы. В то время это было чрезвычайно критично, и одним из способов сократить время работы, как раз являлись inline (встроенные) функции. Сейчас я попробую более менее доступно рассказать почему.
Итак, что из себя представляет обычная функция? Возьмем например простой пример – вычисление факториала.
int factorial ( )
int result = 1 ;
for ( i = 2 ; i < 5 ; i ++ ) return result ;Достаточно простые вычисления факториала (5!) в цикле for, и возврат результата из функции. С++ расценивает эту функцию как некий блок операций, сгруппированный в отдельный блок. Блок этот после компиляции помещается в ячейки памяти единожды в коде, и тело функции (цикл в данном случае) нигде больше в скомпилированной программе не повторяется. Все красиво – получается некий участок памяти, принадлежавший программе, на который процессор при необходимости перескакивает с того места, где находит вызов.
cout << factorial ( ) ;
В данном операторе сия функция задействована, процессор просто выполнит команду ассемблера CALL, в которой будет передан адрес функции. Т.е. вызов функции обойдется в один оператор (если грубо говорить). При этом, в памяти (точнее говоря в стеке программы) занимается место для параметров функции, если они есть, и обязательно для адреса, откуда процессор прыгнул на функцию.
Inline функция избавляет процессор прыгать в ячейку, по адресу которой начинается эта функция. Сам смысл inline состоит в том, чтобы вместо вызова функции подставить ее тело (код функции) в место, где она вызывается.
Если описать наш факториал так:
inline int factorial ( )
int result = 1 ;
for ( i = 2 ; i < 5 ; i ++ ) return result ; cout << factorial ( ) ;мы получим разворот функции в набор операторов:
for ( i = 2 ; i < 5 ; i ++ ) cout << result ;
как будто бы сами в коде написали в этом месте.
Соответственно код типа:
cout << factorial ( ) << factorial ( ) / 5 ; for ( i = 2 ; i < 5 ; i ++ ) cout << result ; for ( i = 2 ; i < 5 ; i ++ ) cout << result / 5 ;
Если мерить философски – количество кода с inline функцией увеличилось. Вместо одной строки вызова функции ее тело, подставленное вместо вызова , дало целых 6 строк. Так же будет и в скомпилированной программе – количество операторов возрастет многократно – на столько, сколько операторов в теле функции и сколько раз ее вписали в программу.
Т.е. различие между inline функцией и обычной функцией – дублирование кода тела функции везде, где она оказывается задействована. В обычной функции, её тело находится в единственном экземпляре в одном и том же месте внутри программы.
Где здесь выгода спросите вы? Экономится время процессора на прыжки с места вызова в тело функции. Если функция огромная и используется в нескольких местах, то inline получается не совсем выгодно. Однако, если функция (тело её) маленькое, с минимальным количеством операторов, решающих задачу, в старину было удобнее отказаться от прыжка и просто подставить их в нужное место, как будто сам программист там их описал.
Как бы там ни было, в бытовых условиях да еще и на современных компьютерах, программы, использующие inline подстановку тела функции вместо вызова, не дают особых преимуществ. Поэтому использовать этот вид функции приходится достаточно редко. ИМХО, ему место в музее славы. Хотя, чтоб быть до конца честным, такой подход дает свои плоды тем, кто программирует контроллеры, процессоры и прочие железяки. Но там свои особенности и свой подход, и в это углубляться сейчас не стоит. Использовать встроенную функцию ( inline функцию) или нет – решать самому программисту. От себя могу добавить только одно – не стоит делать это там, где этого не требуется по заданию.
Встраиваемые функции
В ызов функции, хоть в си он очень быстрый, отнимает некоторое время. В современном си есть возможность объявлять встраиваемые функции. При компиляции вызов функции будет заменён её телом.
Для объявления встраиваемой функции используется ключевое слово 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

Всё ещё не понятно? – пиши вопросы на ящик
C++. Inline функции-члены класса
В языке C++ в классах могут использоваться два типа функций:
- функции, которые подставляются или встроенные функции. Такие функции еще называются inline -функциями;
- обычные функции, код которых не подставляется непосредственно в тело вызывающей программы.
Inline -функции обрабатываются точно также как и макрос. При вызове такой функции с вызывающего кода, тело функции непосредственно вставляется в этот код. Иначе говоря, код inline -функции подставляется в то место строки программы, из которого она вызывается. В результате, вызов inline -функции дает выигрыш во времени выполнения программы (времени обработки функции). Это связано с тем, что исчезают накладные расходы на дополнительную обработку при передаче (получении) параметров в функцию.
2. Какие существуют способы объявления (создания) встроенных ( inline ) функций в определении класса?
Существует два способа объявления (создания) inline -функции.
Способ 1. С использованием модификатора inline . Общий вид функции, объявленной с модификатором inline следующий:
inline returned_type FunName(parameters) < // . >
где returned_type и parameters соответственно тип и параметры, возвращаемые функцией.
Не все компиляторы поддерживают этот способ.
Способ 2. Реализация кода функции-члена класса непосредственно в теле объявления класса. В этом случае использования модификатора inline необязательно.
Общий вид класса с объявленными inline -функциями имеет следующий вид:
class CMyClass < // . // без модификатора inline returned_type MyInlineFun1(parameters) < // . > // с модификатором inline inline returned_type MyInlineFun2(parameters) < // . > // . >
- returned_type – тип, возвращаемый функцией;
- parameters – параметры, получаемые функцией.
3. Пример объявления класса, который содержит встроенные функции ( inline )
В примере объявляется класс CMyPoint . Класс содержит внутренние члены данных а также inline -функцию Get(). Две других функции класса есть обычными, так как реализация этих функций вынесена за границы класса.
// класс, который содержит inline-функции class CMyPoint < int x, y; public: // конструктор класса CMyPoint(void); int GetX(void); // обычная (не inline) функция класса inline int GetY(void) // inline-функция < return y; // реализация в теле класса > // обычная (не inline) функция void SetXY(int nx, int ny); >; // конструктор класса CMyPoint::CMyPoint(void) < x = y = 0; >// реализация не inline-функции GetX() int CMyPoint::GetX(void) < return x; > // реализация обычной не inline функции SetXY() void CMyPoint::SetXY(int nx, int ny)
Использование класса в другом программном коде (например, обработчике события)
CMyPoint MP1; // объект класса, содержащий inline-функцию // вызов обычной (не inline) функции MP1.SetXY(25,30); int tx, ty; tx = MP1.GetX(); // вызов не inline функции ty = MP1.GetY(); // вызов inline функции
4. Какие преимущества дает использование встроенных ( inline ) функций?
Основное преимущество использования встроенных ( inline ) функций – это ускорение времени выполнения программы. Это связано с тем, что при вызове inline -функции не расходуется время на:
- запись аргументов в стек;
- чтение аргументов из стека при возвращении из функции.
5. В каких случаях целесообразно объявлять функции с модификатором inline ?
Добавлять модификатор inline в объявление функции целесообразно в случаях, когда выполняются два основных условия:
- вызов функции происходит настолько часто, что отрицательно влияет на скорость выполнения программы или просто заметно замедляет выполнение программы. Например, функция может вызваться многократно в операторе цикла;
- объем кода функции есть небольшим. Функции, которые имеют большие объемы программного кода существенно увеличивают размер самой программы, что тоже, иногда, нежелательно. Поэтому в качестве встроенных, рекомендуется использовать только очень маленькие по размеру функции.
6. Какие действия выполняет компилятор при объявлении inline-функции?
Объявление inline -функции есть запросом а не командой. Поэтому, компилятор может не выполнить запрос на генерирование кода для объявленной inline -функции. В этом случае, функция может быть использована как обычная (не inline ).
Например:
- в большинстве случаев рекурсивные функции не могут быть использованы как inline -функции;
- нельзя сгенерировать inline функцию, которая содержит статические члены данных.
7. Причины, которые могут привести к игнорированию ключевого слова inline
В некоторых случаях компилятор не может определить функцию как встроенную и просто игнорирует ключевое слово inline . Ниже перечислены возможные причины такого поведения компилятора:
- Слишком большой размер функции.
- Функция есть рекурсивной.
- В одном и том же выражении функция может повторяться несколько раз.
- В функции используются управляющие операторы switch , if или операторы цикла.
Связанные темы
- Описание функции. Фактические и формальные параметры. Передача параметров в функцию по значению и по адресу. Прототип функции
- Виды функций-членов класса. Обычные функции-члены. Статические ( static ) функции-члены класса. Примеры использования