スタックを使用すると、有限数のレジスタによって課される制限をエレガントにバイパスできます。
正確に26個のグローバル「レジスタa〜z」(または8080チップの7バイトサイズのレジスタのみ)を想像してください。このアプリで作成するすべての関数は、このフラットリストを共有します。
素朴なスタートは、最初のいくつかのレジスタを最初の関数に割り当て、それが3つしかかからないことを知って、2番目の関数の「d」から始めます...すぐに使い果たされます。
代わりに、チューリングマシンのような比phor的なテープがある場合は、使用しているすべての変数を保存してテープをforward()することにより、各関数が「別の関数を呼び出す」ようにできます。必要に応じて登録します。呼び出し先が終了すると、制御を親関数に返します。親関数は、必要に応じて呼び出し先の出力をどこに取り込むかを知っており、テープを逆方向に再生してその状態を復元します。
基本的な呼び出しフレームはそれだけであり、コンパイラが1つの関数から別の関数への遷移の前後に配置する標準化されたマシンコードシーケンスによって作成および削除されます。(Cスタックフレームを思い出さなければならないのは久しぶりですが、X86_calling_conventionsで誰が何を落とすのかというさまざまな方法を読むことができます。)
(再帰は素晴らしいですが、スタックなしでレジスタをジャグリングしなければならなかったなら、本当にスタックに感謝するでしょう。)
プログラムを格納し、コンパイルをサポートするために必要なハードディスク領域とRAMの増加が(それぞれ)呼び出しスタックを使用する理由だと思います。あれは正しいですか?
最近はインライン化できますが(ビデオストリームの世界では、「高速」は常に優れています。「アセンブリの数KBを少なくする」という意味はほとんどありません)、主な制限は、特定のタイプのコードパターンでフラット化するコンパイラーの能力にあります。
たとえば、ポリモーフィックオブジェクト-渡されるオブジェクトの唯一のタイプがわからない場合、フラット化できません。オブジェクトの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);
}
上記では、コンパイラーは、act()の内部にあるものを介してInlineMeから静的にインライン化することを選択することも、実行時にvtableに触れる必要もありません。
ただし、オブジェクトのフレーバーの不確実性により、同じ関数の他の呼び出しがインライン化されている場合でも、離散関数への呼び出しとして残されます。