Стеки позволяют нам элегантно обходить ограничения, налагаемые конечным числом регистров.
Представьте, что у вас есть ровно 26 глобальных «регистров az» (или даже только 7-байтовые регистры микросхемы 8080). И каждая функция, которую вы пишете в этом приложении, разделяет этот плоский список.
Наивным началом было бы выделение первых нескольких регистров для первой функции, и, зная, что это заняло всего 3, начните с «d» для второй функции ... Вы быстро заканчиваете.
Вместо этого, если у вас есть метафорическая лента, такая как машина Тьюринга, вы можете заставить каждую функцию запускать «вызов другой функции», сохраняя все переменные, которые она использует, и пересылать () ленту, а затем функция вызываемого абонента может запутаться с таким количеством регистрируется как хочет. Когда вызываемый абонент завершает работу, он возвращает управление родительской функции, которая знает, где можно получить выходные данные вызываемого абонента, а затем воспроизводит ленту назад, чтобы восстановить ее состояние.
Ваш основной фрейм вызова - именно это, он создается и отбрасывается стандартизированными последовательностями машинного кода, которые компилятор вводит вокруг переходов от одной функции к другой. (Прошло много времени с тех пор, как мне приходилось запоминать свои кадры стека C, но вы можете прочитать о различных способах определения того, кто отбрасывает то, что в X86_calling_conventions .)
(рекурсия великолепна, но если бы вам когда-либо приходилось манипулировать регистрами без стека, вы бы по- настоящему оценили стеки.)
Я предполагаю, что увеличение пространства на жестком диске и оперативной памяти, необходимых для хранения программы и поддержки ее компиляции (соответственно), является причиной, по которой мы используем стеки вызовов. Это верно?
Несмотря на то, что в наши дни мы можем встроить больше, («большая скорость» всегда хороша, «меньшее количество килобайт сборок» означает очень мало в мире видеопотоков) Основное ограничение заключается в способности компилятора сглаживать определенные типы шаблонов кода.
Например, полиморфные объекты - если вы не знаете один-единственный тип объекта, который вам передадут, вы не сможете сплющить; Вы должны посмотреть на vtable возможностей объекта и вызвать через этот указатель ... тривиально, чтобы сделать во время выполнения, невозможно встроить во время компиляции.
Современный набор инструментов может успешно встроить полиморфно определенную функцию, когда он сгладил достаточно вызывающего (-ых) вызова (-ов), чтобы точно знать , какой вариант obj:
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 и заканчивая тем, что находится внутри act (), а также не нужно трогать какие-либо виртуальные таблицы во время выполнения.
Но любая неопределенность в какой аромат объекта оставит его как призыв к дискретной функции, даже если некоторые другие вызовы той же функции являются встраиваемыми.