Стеки дозволяють елегантно обійти межі, накладені кінцевою кількістю регістрів.
Уявіть, що ви маєте рівно 26 глобальних «регістрів az» (або навіть майте лише 7 байтових регістрів чіпа 8080). І кожна функція, яку ви пишете в цьому додатку, поділяє цей плоский список.
Наївним початком буде виділення перших кількох регістрів на першу функцію, і знаючи, що це знадобилося лише 3, почніть з "d" для другої функції ... Ви швидко закінчитеся.
Натомість, якщо у вас є метафорична стрічка, як, наприклад, машина Тьюрінга, ви могли б змусити кожну функцію запустити "виклик іншої функції", зберігаючи всі змінні, якими вона користується і пересувати () стрічку, і тоді функція callee може заплутатися з якоюсь кількістю реєструється як хоче. Після закінчення виклику він повертає контроль до батьківської функції, яка знає, куди потрібно зафіксувати висновок виклику, а потім відтворює стрічку назад, щоб відновити її стан.
Ваш базовий кадр виклику саме такий, і вони створюються та скидаються за допомогою стандартизованих послідовностей машинного коду, який компілятор здійснює навколо переходів від однієї функції до іншої. (Минуло давно, коли мені довелося запам’ятати свої кадри стека C, але ви можете прочитати на різних способах обов'язки того, хто скидає те, що на X86_calling_conventions .)
(рекурсія є приголомшливою, але якби вам коли-небудь довелося жонглювати регістри без стека, ви б дуже цінували стеки.)
Я вважаю, що збільшення місця на жорсткому диску та оперативної пам’яті, необхідних для зберігання програми та підтримки її компіляції (відповідно), є причиною, чому ми використовуємо стеки викликів. Це правильно?
Хоча в наші дні ми можемо вкласти більше, ("більша швидкість" завжди хороша; "менше кб збірки" означає дуже мало у світі відеопотоків) Основне обмеження полягає у здатності компілятора згладжувати певні типи моделей коду.
Наприклад, поліморфні об'єкти - якщо ви не знаєте одного і єдиного типу об'єкта, який вам передадуть, ви не можете згладити; ви повинні подивитися на vtable-функції об'єктів і зателефонувати через цей вказівник ... тривіально робити під час виконання, неможливо вбудувати в час компіляції.
Сучасний інструментальний ланцюг може радісно вбудовувати певну поліморфно функцію, коли він достатньо сплюндрував абонента, щоб точно знати , який аромат об'єкта:
class Base {
public: void act() = 0;
};
class Child1: public Base {
public: void act() {};
};
void ActOn(Base* something) {
something->act();
}
void InlineMe() {
Child1 thingamabob;
ActOn(&thingamabob);
}
у вищевикладеному, компілятор може вибрати право підтримувати статичне вбудовування від InlineMe за допомогою внутрішнього акта (), а також не потрібно торкатися будь-яких vtables під час виконання.
Але будь-яка невизначеність в який аромат об'єкта залишить його як заклик до дискретної функції, навіть якщо деякі інші виклики тієї ж функції є вбудованими.