Inline функции весьма занятный рудимент, доставшийся современному миру из уже далеких бандитских 90-х. Когда процветал ассемблер, Си компилировал очень компактные и маленькие программы, когда процессоры были слабенькими (по сравнению с тем, что сейчас в мобилки ставят к примеру) и время выполнения кода ценилось на вес золота. Этот тип функций вообще-то использовался не только в Си, и в то лихое время оправдывал себя.
Я думаю, не для кого не секрет, что древние компьютеры требовали при написании ПО для тяжелых вычислений достаточно таки серьезного подхода с позиции программиста. Выкручиваться и экономить приходилось на всём, иначе время работы программы увеличивалось в разы. Это сейчас мы гоняем гигабайтные игрушки не особо жалуясь на скорость работы. В то время это было чрезвычайно критично, и одним из способов сократить время работы, как раз являлись inline (встроенные) функции. Сейчас я попробую более менее доступно рассказать почему.
Итак, что из себя представляет обычная функция? Возьмем например простой пример – вычисление факториала.
1 2 3 4 5 6 7 8 | int factorial() { int i = 0; int result = 1; for(i = 2;i < 5; i++) result *= i; return result; } |
Достаточно простые вычисления факториала (5!) в цикле for, и возврат результата из функции. С++ расценивает эту функцию как некий блок операций, сгруппированный в отдельный блок. Блок этот после компиляции помещается в ячейки памяти единожды в коде, и тело функции (цикл в данном случае) нигде больше в скомпилированной программе не повторяется. Все красиво – получается некий участок памяти, принадлежавший программе, на который процессор при необходимости перескакивает с того места, где находит вызов.
1 | cout << factorial(); |
В данном операторе сия функция задействована, процессор просто выполнит команду ассемблера CALL, в которой будет передан адрес функции. Т.е. вызов функции обойдется в один оператор (если грубо говорить). При этом, в памяти (точнее говоря в стеке программы) занимается место для параметров функции, если они есть, и обязательно для адреса, откуда процессор прыгнул на функцию.
Inline функция избавляет процессор прыгать в ячейку, по адресу которой начинается эта функция. Сам смысл inline состоит в том, чтобы вместо вызова функции подставить ее тело (код функции) в место, где она вызывается.
Если описать наш факториал так:
1 2 3 4 5 6 7 8 | inline int factorial() { int i = 0; int result = 1; for(i = 2; i < 5; i++) result *= i; return result; } |
То вместо
1 | cout << factorial(); |
мы получим разворот функции в набор операторов:
1 2 3 4 5 | int i = 0; result = 1; for(i = 2; i < 5; i++) result *= i; cout << result; |
как будто бы сами в коде написали в этом месте.
Соответственно код типа:
1 | cout << factorial() << factorial() / 5; |
превратится в
1 2 3 4 5 6 7 8 9 | int i = 0; result = 1; for(i = 2; i < 5; i++) result *= i; cout << result; result = 1; for(i = 2; i < 5; i++) result *= i cout << result / 5; |
Если мерить философски – количество кода с inline функцией увеличилось. Вместо одной строки вызова функции ее тело, подставленное вместо вызова , дало целых 6 строк. Так же будет и в скомпилированной программе – количество операторов возрастет многократно – на столько, сколько операторов в теле функции и сколько раз ее вписали в программу.
Т.е. различие между inline функцией и обычной функцией – дублирование кода тела функции везде, где она оказывается задействована. В обычной функции, её тело находится в единственном экземпляре в одном и том же месте внутри программы.
Где здесь выгода спросите вы? Экономится время процессора на прыжки с места вызова в тело функции. Если функция огромная и используется в нескольких местах, то inline получается не совсем выгодно. Однако, если функция (тело её) маленькое, с минимальным количеством операторов, решающих задачу, в старину было удобнее отказаться от прыжка и просто подставить их в нужное место, как будто сам программист там их описал.
Как бы там ни было, в бытовых условиях да еще и на современных компьютерах, программы, использующие inline подстановку тела функции вместо вызова, не дают особых преимуществ. Поэтому использовать этот вид функции приходится достаточно редко. ИМХО, ему место в музее славы. Хотя, чтоб быть до конца честным, такой подход дает свои плоды тем, кто программирует контроллеры, процессоры и прочие железяки. Но там свои особенности и свой подход, и в это углубляться сейчас не стоит. Использовать встроенную функцию (inline функцию) или нет – решать самому программисту. От себя могу добавить только одно – не стоит делать это там, где этого не требуется по заданию.
> Поэтому использовать этот вид функции приходится достаточно редко.
Использовать inline функции явно приходится использовать, возможно, и не так часто. Но они массово используются неявно при определении классов и создании объектов:
– любая функция-метод, определение котрой находится внутри определения её класса, неявно получает квалификатор inline.
На примере класса, определяющего 2D-точку (на плоскости):
class point {
private:
float x, y;
public:
...
float operator -( const point& p ) { // расстояние 2-х 2D
float dx = ( x - p.x ), dy = ( y - p.y );
return sqrtf( dx * dx + dy * dy );
}
};
И альтернативное 2-е определение, очень похожее:
class point {
private:
float x, y;
public:
...
float operator -( const point& p ); // расстояние 2-х 2D
};
float point::operator -( const point& p ) {
float dx = ( x - p.x ), dy = ( y - p.y );
return sqrtf( dx * dx + dy * dy );
};
В 1-м варианте расстояние между 2-мя точками будет везде вычисляться inline, а во втором – вызовом функции-метода.
Конечно код в комментариях отображается – это ужас … но, надеюсь, будет понятно.
Это уже забота компилятора. Имеется ввиду самому программисту редко приходится в бытовых условиях использовать этот вид функций.
Нет. Это как раз тот случай, когда компилятору запрещено вмешиваться в реализацию метода: если метод описан внутри класса – inline, а если внутри метод только объявляе, а реализация либо вне объявления класса или в отдельном файле (что чаще) – функцимональный вызов.
Это как-раз случай, когда реализация находится полностью под контролем автора, а автор должен знать чем его определения обернутся.
Принцип инлайнов не меняется в зависимости от того, в ООП его применяют или в функционалке )
К чему такие усложнения теории? Я не против уточнений, но тогда нужна еще одна статья, описывающая разницу между тем и этим случаями. Нет никакой надобности мешать всю теорию в кучу – получится тот же самый MSDN.
ещё одной “мелочью” является то, что квалификатор inline является рекомендательным: компилятор использует его только если это возможно, если он считает, что это недопустимо, он использует функциональный вызов без всякого на то укедомленя (даже предупреждением).
Пример – определите функцию факториала так:
inline unsigned long long fact3( int n ) {
return 1 == n ? 1 : n * fact3( n - 1 );
}
Здесь inline не сыграет никакой роли.
Ну то же самое. В современном Си компилятор сам решает. В Си для 8086 или 80286 не все компиляторы так оптимизировали.
Ещё одна “мелочь”: функции с квалификатором inline замечательно работают, но до тех пор, пока определение функции и вызов находятся в одном файле кода. При росте проекта, стоит разделить код на несколько файлов (раздельная компиляия с последующим связыванием объектных файлов), и это создаст огромные проблемы с поиском ошибки: inline функци не пригодны для внешнего связывания.
inline – это определитель макроопределения, и они непригодны для внешнего связывания (линкования).
> Этот тип функций вообще-то использовался не только в Си, и в то лихое время оправдывал себя.
В стандарте языка C вообще нет ключевого слова inline, там подобные вещи реализуются через параметризированные макросы #define.
В некоторых поздних компиляторах C inline включено, но только для пддержания совместимсоти с C++.
Поэтому в принципе неверно говорить, что inline заимствовано из C, это – чисто C++ приобретение.
Поэтому я бы вот то “залихватское” начало выбросил.
А я не намекал на стандарт. Я намекал на ЯВУ. В Паскале тоже такие функции существовали. Да и в бейсике кажется было что-то подобное в поздних его видах.
> Этот тип функций вообще-то использовался не только в Си
В языке C не было ключевого слова inline от момента, когда его в 1969г. начал формулировать Денис Ритчи, и почти до 2000-х годов.
Всё это время если слов inline появилось программе C, оно бы вызвало синтаксическую ошибку.
К вопросу совместимости C и C++ … в отношении inline:
Если компилировать код как C, но с ключом -std=c89 (т.е. стандарт ANSI 1989г.), то на inline просто будет синтаксическая ошибка. И на всех более ранних стандартах языка C.
Использование inline в C допустит только компилятор с опцией -std=c99, т.е. такое ключевое слово допускается стандартом C только после 1999г.
Вообще то, статья про inline функции, в отличие от большинства статей сайта – неудачная. Мне кажется, что её следовало бы слегка скорректировать, добавив и исправив с учётом указанных позиций.
Я, когда ее писал, не желал вдаваться в особые подробности, чтоб не загораживать ими саму суть. Не зря в интернете есть правило “Многабукаф”, которое зачастую раздувает контент в салат Ой-Ливьйо.
inline также не будет работать с рекурсивными функциями и т.п. У inline много нюансов .
я делаю встроенными только критичные одна строчные функции
inline удобен когда работаешь с прерываниями на контроллерах. Там если тело прерывания вынести в функцию, ее может перебить другое прерывание так как фактически ты выходишь из обработчика прерывания в другое место. inline позволяет этого избежать.