6

これは非常に単純な質問かもしれませんが、ここで答えを見つけることができず、答えを尋ねた人も知りませんでした:

次のような簡単な C# メソッドを記述できます。

private void foo()
{
   int a = 1;
   int b = 5;
}

(コンパイラによって作成された) CIL コードが共通言語ランタイムによって実行されると、実行中のコントロールがメソッド内にある間に、スタックの一番上に次のフィールドが作成されます。

b = 5
a = 1

しかし今、「a」というフィールドにアクセスするメソッドを次のように拡張します。

private void foo()
{
   int a = 1;
   int b = 5;
   Console.WriteLine(a);
}

現在、CLR はスタックの最上位にないフィールドにアクセスする必要がありますが、FILO (先入れ後出し) の原則に従って、アクセスする前に、要求されたフィールドより上のすべてのフィールドを処理する必要があります。

要求されたフィールド「a」の上のスタックにある「b」というフィールドはどうなりますか?

後で実行中のメソッドによって使用される可能性があるため、CLR はそれを削除できません。

私の知る限り、フィールド、スタック、またはヒープを格納する方法は2つしかありません。これをヒープに移動しても、CLR からのスタックからすべての利点が得られるため、あまり意味がありません。CLR は 2 番目のスタックのようなものを作成しますか?

それはどのように正確に機能しますか?

-編集-

たぶん、私は自分の意図を十分に明確に説明していませんでした。

このようなメソッドを書くと:

private void foo()
{
   int a = 1;
   int b = 5;
   Console.WriteLine(a);
   Console.WriteLine(b);
}

CLR は最初にスタックに 2 つのフィールドを書き込み、その後それらにアクセスしますが、順序が逆になります。

まず、フィールド "a" にアクセスする必要がありますが、そこに到達するために、CLR はスタック上のフィールド "a" の上にあるフィールド "b" を処理する必要があります。後でアクセスする必要があるため、スタックからフィールド「b」を削除することはできません。

それはどのように機能しますか?

4

6 に答える 6

7

変数は個別にスタックされません。スタックには「フレーム」が含まれています。各フレームには、現在のメソッド呼び出しに必要なすべての変数(ローカル、パラメーターなど)が含まれています。したがって、あなたの例では、同じフレーム内に互いに並んで存在し、どちらも削除する必要はありませんabメソッドfooが完了すると、スタックフレーム全体がスタックからポップされ、呼び出し元のメソッドのフレームが一番上に残ります。

ウィキペディアの記事はいくつかの啓蒙を提供するかもしれません。

于 2012-07-31T13:44:11.927 に答える
4

呼び出しスタックは、厳密には、最上位の要素とのみ対話できる「純粋な」スタックではありません。呼び出しスタックでは、変数ではなく、関数呼び出し全体または変数スコープ全体、あるいはその両方をスタックします。

たとえば、新しい関数、たとえばfoo()が呼び出された場合、その2つの変数、aおよびbはスタックの最上位に配置され、それらに完全にアクセスできます。(通常)スタック上のこれらの変数の下にあるものは何も認識していません。

このコードを見てみましょう:

void foo() { // << Space is allocated on the stack for a and b.
             // << Anything in this scope has full access to a and b.
             // << But you cannot (normally) access anything from the
             // << calling function.
    var a = 1;
    var b = 2;

    if (a == 1) {  // << Another variable scope is placed on the stack.
                   // << From here you can access a, b and c.
        var c = 3;
    } // << c is removed from the stack.
} // << a, b and anything else in foo() is removed from the stack.
于 2012-07-31T13:44:47.520 に答える
4

スタックについて間違ったイメージを持っています。メソッド呼び出し間のスタックのようにしか機能しません。メソッド内では、スタック フレームはローカル変数の配列のように機能します。マネージ コードのスタック フレームについても特別なことはなく、ネイティブ C または C++ コードで使用されるスタック フレームとまったく同じように動作します。

ローカル変数には、スタック フレーム ポインタである EBP レジスタからの固定オフセットがあります。そのオフセットは、JIT コンパイラによって決定されます。

投稿したコードの具体的な結果は、ジャストインタイム コンパイラに組み込まれたオプティマイザが、使用されていないローカル変数を削除するだけであるということです。最後の例のa変数は、最終的に cpu レジスタに置かれる可能性が非常に高く、スタックには決して置かれません。標準的な最適化。

于 2012-07-31T13:50:09.547 に答える
3

フィールドについて話している間、ローカル変数ab呼ばれることに注意してください。

おそらく、次の単純化された論理表現が問題を解決する可能性があります。を呼び出す前はConsole.WriteLine、スタックの一番上は次のようになります。

|5| // b
|1| // a

内部Console.WriteLineでは、追加のスタックフレームがそのパラメーターに追加されます (value変数 のコピーを取得すると呼ばれますa)。

|1| // value = a
|5| // b
|1| // a

Console.WriteLine が戻ると、一番上のフレームがポップされ、スタックは再び次のようになります。

|5| // b
|1| // a
于 2012-07-31T13:46:34.763 に答える
1

CLRに関しては、ローカル変数をメールボックスのように番号が付けられた「スロット」と考える方がよいでしょう。それらの「スロット」に格納された値がメソッドのスタックフレームに収まるか(ここでは他の値がその概念をカバーしています)、CPUレジスタに格納されるか、完全に最適化されるかは、ジッターの詳細です。詳細については、ILStlocの説明を参照してください。

実行中の命令に基づいて値がポップおよびプッシュされる、実行スタックを実行するCLRについて考えることをお勧めします。マネージコードがCPU上でどのように調整および実行されるかについての基本的な詳細は、従来のスタックフレーム、レジスタ、およびポインタの逆参照が再び機能する場所であるという別の問題です。ただし、ILレベルでのCLRの観点からは、これらのことは(ほとんど)重要ではありません。

于 2012-07-31T13:53:22.707 に答える
1

C# のローカル変数、CIL のローカル変数、CIL のスタック、およびネイティブ スタックという 4 つの関連するが異なる概念があります。

C# ローカルが CIL にマップされる方法、および CIL ローカルとスタックがネイティブ メモリにマップされる方法は実装定義であるため、これに依存しないでください。

C# ローカルとは何かを知っています。それらは CIL ローカルとして表すことができますが、通常は CIL スタックには進みません (そうする C# コンパイラの最適化がいくつかある可能性があります)。しかし、他にもいくつかのオプションがあります。ローカルが必要ない場合は、完全に最適化して削除するか、言いようのない名前 (ラムダのクロージャ変数、yieldメソッドまたはasyncメソッド内の変数) を持つクラスのフィールドとしてコンパイルすることができます。また、一部の C# ローカルが CIL ローカルとしてコンパイルされている場合でも、1 対 1 でマップする必要はありません。これは、コンパイラがそれが安全であることを認識していれば、1 つの CIL ローカルを複数の C# ローカルに使用できるためです。

CIL には、ローカル変数とスタックがあります。ローカル変数はスタックから完全に分離されており、それぞれを操作するための異なる CIL 命令があります。ローカル変数は、必要な値を長期間保持するために使用され、各ローカルにはいつでもアクセスできます。CIL スタックには、現在使用されているほとんどの値 (命令へのパラメーターとその戻り値) が含まれています。スタックでは、トップ値のみにアクセスできます。

CIL ローカルと CIL スタックの両方が実際にはネイティブ スタックに配置されますが、適合する場合は、多くの場合、単にレジスタに配置されます。もちろん、JIT コンパイラーは他の最適化も行うことができます。他の人が言ったように、現在のメソッドのスタック内の値は、トップだけでなく、いつでもアクセスできます。

于 2012-08-02T08:52:15.260 に答える