Inline функції вельми цікавий рудимент, дістався сучасному світові з уже далеких бандитських 90-х. Коли процвітав асемблер, Сі компілював дуже компактні і маленькі програми, коли процесори були слабенькими (в порівнянні з тим, що зараз в мобілки ставлять наприклад) і час виконання коду цінувалося на вагу золота. Цей тип функцій взагалі-то використовувався не тільки в Сі, і в той лихий час виправдовував себе.
Я вважаю, не для кого не секрет, що стародавні комп'ютери вимагали при написанні ПЗ для важких обчислень досить таки серйозного підходу з позиції програміста. Викручуватися і економити доводилося на всьому, інакше час роботи програми збільшувалася в рази. Це зараз ми ганяємо гігабайтні іграшки не особливо скаржачись на швидкість роботи. У той час це було надзвичайно критично, і одним із способів скоротити час роботи, як раз були в лінію (встроенные) функции. Зараз я спробую більш менш доступно розповісти чому.
Итак, що з себе представляє звичайна функція? Візьмемо наприклад простий приклад – обчислення факторіала.
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, в якій буде переданий адреса функції. Тобто,. виклик функції обійдеться в один оператор (якщо грубо говорити). При этом, в памяти (точніше кажучи в стеці програми) займається місце для параметрів функції, якщо вони є, і обов'язково для адреси, звідки процесор стрибнув на функцію.
В лінію функція позбавляє процесор стрибати в клітинку, за адресою якої починається ця функція. сам сенс в лінію полягає в тому, щоб замість виклику функції підставити її тіло (код функції) в місце, де вона викликається.
Якщо описати наш факторіал так:
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; |
Якщо міряти філософськи – кількість коду з в лінію функцією збільшилася. Замість одного рядка виклику функції її тіло, підставлену замість виклику , дало цілих 6 строк. Так само буде і в компільованою програмі – кількість операторів зросте багаторазово – на стільки, скільки операторів в тілі функції і скільки разів її вписали в програму.
Тобто,. відмінність між 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, а в другому – викликом функції-методу.
Звичайно код в коментарях відображається – це жах … але, сподіваюся, буде зрозуміло.
Це вже турбота компілятора. Мається на увазі самому програмісту рідко доводиться в побутових умовах використовувати цей вид функцій.
Нет. Це як раз той випадок, коли компілятору заборонено втручатися в реалізацію методу: якщо метод описаний всередині класу – в лінію, а якщо всередині метод тільки оголошується, а реалізація або поза оголошення класу або в окремому файлі (що частіше) – функцімональний виклик.
Це як-раз випадок, коли реалізація знаходиться повністю під контролем автора, а автор повинен знати чим його визначення обернуться.
Принцип інлайн не змінюється в залежності від того, в ООП його застосовують або в функціоналки )
До чого такі ускладнення теорії? Я не проти уточнень, але тоді потрібна ще одна стаття, описує різницю між тим і цим випадками. Немає ніякої потреби заважати всю теорію в купу – вийде той же самий MSDN.
ще однією “дрібницею” є те, що кваліфікатор inline є рекомендаційним: компілятор використовує його лише тоді, коли це можливо, якщо він вважає, що це неприпустимо, він використовує функціональний виклик без всякого на те укедомленя (навіть попередженням).
Приклад – визначте функцію факторіала так:
inline unsigned long long fact3( int n ) {
return 1 == n ? 1 : n * fact3( n - 1 );
}
Тут inline не зіграє ніякої ролі.
Ну то ж саме. У сучасному Сі компілятор сам вирішує. У своєму DLYA 8086 или 80286 не всі компілятори так оптимізували.
Ще одна “дрібниця”: функції з кваліфікаторів 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 дозволяє цього уникнути.