スコープ言語は、特定のスコープまたは関数のローカル変数を、アクティベーションレコードと呼ばれるデータ構造に一緒に格納する傾向があります。スタックフレームは、アクティベーションレコードのインスタンスの例です。すべてのアクティベーションレコードがスタックの一部であるわけではありません。たとえば、ECMAScript/JavaScriptではガベージコレクションされ、関数オブジェクトを介してスコープを再入力できます。
強力に型付けされた言語は、機能が大幅に制限されていても、アクティブ化レコードがファーストクラスのオブジェクトとして存在しますか?これはどのような考慮事項を提起する傾向がありますか?たとえば、次の関数は次のとおりです。
void f( int i ) {
int j = i + 3;
{
int * q = & j;
}
{
char r = 'z';
}
}
…このデータ型にマップできます:
struct f_activation {
int i;
int j;
union {
int * q;
char r;
};
};
これは単なる例です。最適化コンパイラは、そのような制限的なマッピングを特定することを望まないでしょう。
アセンブリ言語プログラマは、他のデータ型と同じrecord
ディレクティブを使用してアクティベーションレコードを宣言することがよくあります。 TCLには uplevel
コマンドがあり、これは呼び出し元の関数のスコープをオブジェクトとして扱うのに十分です。 (私はこれが行われるのを見ました。冗談ではありません。)
質問のコンテキスト:C++コミュニティは、将来の言語リビジョンにコルーチンを追加しようとしています。 C++にはすでに人気のあるデザインパターン「関数オブジェクト」があり、多くの人は一時停止したコルーチンもオブジェクトのように機能することを望んでいます。理想的には、シリアル化とクリーンな強制終了を使用します。
私は洗濯物のリストを探していませんが、この言語デザインの方向性に関する過去の経験を探しています。しかし、私は参考文献を得て、自分自身をより深く読むことができてうれしいです。
興味深い質問(1990年の博士論文、ところで勉強しました)。アクティベーションレコード(IIRCが 入れ子関数 sの表示に関連する)ではなく、呼び出しフレーム( 呼び出しスタック )について説明します。
これは continuations にかなり関連しており、継続を具体化するいくつかの言語(特にScheme)があります。ほとんどの場合、継続は抽象的と見なされます(たとえば、 [〜#〜] cps [〜#〜] または call/cc に関連して)。場合によっては検査可能です(または変更可能です)。 動的ソフトウェア更新 、 永続性 、 アプリケーションチェックポインティング 、 LISPマシン についてもお読みください。
古いA.Appelの本も読んでください 継続でコンパイル &彼の古い論文 ガベージコレクションはより速くすることができますスタック割り当て 。 Flanagan&alの論文を読んでください 継続を伴うコンパイルの本質 。
C.Queinnec LISP In Small Pieces 本を読んで、質問に関連するさまざまな戦略とトレードオフを説明してください。
クロージャ (関数の抽象化に使用され、 無名関数 )はヒープに割り当てられることが多いことに注意してください(通常、効率的な ガベージコレクション の実装が必要です)。 Ocamlを調べてください。
言語のいくつかの実装は、少なくともデバッグ目的で、GNU libc backtrace関数 。インスタンスをJVM( この質問 を参照)および [〜#〜] jpda [〜#〜] を探します。IIRC、Smalltalkは明示的に修正しました呼び出しスタック。
ほとんどの呼び出し規約ではすべての引数を指定する必要がないため、実際的な問題は効率です(特に、本物のマシンコードがある場合、たとえば [〜#〜] jit [〜#〜] テクニックで生成される場合)。プロセッサスタックを通過し、それらのいくつかはレジスタを通過するだけです(スタック上に こぼれた ではない場合もあります)。 効率の考慮と既存の [〜#〜] abi [〜#〜] sおよび 呼び出し規約 sとの互換性は、標準の欠如を説明できるコールスタックイントロスペクションテクニック(効率的なレジスタ割り当ては、フルスタックイントロスペクションへのhostileであるため)。
ところで、正確な ガベージコレクター は、コールスタックのイントロスペクションが必要です(GCされた値またはオブジェクトを指すローカル変数をトレースする必要があるため)。したがって、Schemeで完全にまたはほとんど記述されているSchemeのいくつかの実装も調べてください(例: Bones 、 S48 、 Chicken )。
[〜#〜] sbcl [〜#〜]Common LISP の実装はコールスタックをイントロスペクトすることもできます(そしてそのデバッガーとコンパイラーの両方がSBCLで書かれています) 。
Lua & Neko & Parrot も確認してください。 IIRC、それらのバイトコードVMはイントロスペクト可能です(スタックを含む)。
Lisaac プロジェクトも調べてください。
ほぼ廃止された setcontext(3) (Linuxおよび他の一部のUnix)および longjmp(3) も参照してください。 C++ デストラクタ および 例外 (および スレッド )が呼び出しスタックに関連していることに注意してください。
J.Pitratの本の技術付録 Artificial Beings、Conscience of a Conscious Machine と彼の blog を読んでください。これらには、コールスタックのイントロスペクトが実際に役立つ理由がいくつか説明されています。
ところで、私の [〜#〜] melt [〜#〜] もの(カスタマイズするLispyドメイン固有の言語 [〜#〜] gcc [〜#〜] )ほぼ内省可能なコールスタックがあります(内部実装にはありますが、まだ公開していない可能性があります)。
はい、しかしあなたが考えている方法ではありません。これはclosuresの不可欠な部分であり、多くの言語がこれを利用しています。クロージャは、囲んでいるメソッドのアクティベーションレコードの一部またはすべてを新しいオブジェクトに移動することで実装されます。新しいオブジェクトはどこかに渡され、そのオブジェクトでメソッドが呼び出されます。このオブジェクトは、クローズされたアクティベーションレコードのデータにアクセスできます。
そのような言語の少なくとも1つであるDelphiでは、RTTIを使用してクロージャーのアクティブ化レコードのメンバーを取得することができます。これは、質問に記載されているシリアル化のユースケースに役立ちます。同様のことは、おそらくリフレクションを使用して.NET言語で実行できます。