「値型はスタックに割り当てられますが、参照型はマネージド ヒープ上にあります。」
クラスのメソッド内にローカル変数 (int a=2; など) がある場合、どこに割り当てられますか?
この例では、値型が参照型に含まれています。参照はマネージド ヒープに存在するため、ここでの値の型 (int a) もスタックではなくマネージド ヒープにあると想定しています。
ここで何か不足していますか?
「値型はスタックに割り当てられますが、参照型はマネージド ヒープ上にあります。」
クラスのメソッド内にローカル変数 (int a=2; など) がある場合、どこに割り当てられますか?
この例では、値型が参照型に含まれています。参照はマネージド ヒープに存在するため、ここでの値の型 (int a) もスタックではなくマネージド ヒープにあると想定しています。
ここで何か不足していますか?
ローカル変数が格納される場所を決定するのは、主に JIT コンパイラです。これはアーキテクチャの実装の詳細が重いため、x86 ジッタに限定しましょう。それが行う一般的な選択:
どこにもありません。これは、あなたが示した非常に単純な例で発生します。ジッターオプティマイザーは、ローカル変数が初期化されているがどこにも使用されていないことを確認でき、それを削除します。
CPU レジスタ内。これは非常に重要な最適化です。より高速なストレージの場所はありません。変数は、ほとんどの場合、メソッド本体が実行される時間の少なくとも一部でレジスタ内に存在します。これは、多くの cpu 命令でオペランドが最初にレジスタに存在する必要があるためです。ジッターは、レジスターが不足し (x86 には多くない)、別の操作のためにレジスターを再利用する必要がある場合にのみ、変数をスタック フレームにスピルします。
メソッドのスタック フレーム内。誰もがそれについて考える伝統的な方法。変数は、EBP レジスタからの固定オフセットに格納されます。これらの変数へのアクセスは非常に高速ですが、レジスタに格納されている場合ほど高速ではありません。
ただし、言語でローカル スコープを持つ変数の格納場所にコンパイラが影響を与える方法についても説明する必要があります (Marc に感謝します)。
ガベージ コレクション ヒープ上。これは、コンパイラがコードを書き直して反復子を実装するとき、匿名メソッドまたはラムダ式の変数をキャプチャするとき、またはasyncキーワードでマークされたメソッドを実装するときに、コンパイラによって行われます。ローカル変数は、通常どおりヒープに割り当てられる隠しクラスのフィールドになります。
ローダーヒープ内。C# プログラマには奇抜ですが、Static キーワードを使用して VB.NET コンパイラでサポートされています。コンパイラによって実装される機能で、C# の静的フィールドのように機能しますが、スコープはメソッド本体に限定されます。スレッドから呼び出された場合でも、正しく初期化されるように自動生成されたコードが多数あります。
これは、変数の可能な保存場所のほぼすべてをカバーしています :) [ThreadStatic] の例を思い付くのに苦労していますが。これは、情報過多の可能性があるケースです。最も一般的な方法については、箇条書き 2 と 3 に注目してください。そして確かに箇条書き 3 は、マネージ コードが機能する方法について生産的に考えることです。
まず、投稿の最初の行が誤解を招き、不完全で不正確であることに注意してください。値型はほとんどどこにでも置くことができます。
この例では、値型は参照型に含まれています。
ここでの「含まれている」は誤解を招く恐れがあります。これを混同している「内に含まれる」は「インスタンスフィールド」です。これは、メソッドのローカル変数には適用されません。メソッドローカル変数は、実装の詳細として、スタック上に存在します...そうでない場合を除きます!これには、イテレータブロックとキャプチャされた変数が含まれます。あなたはそれらのどちらにも言及していないので、答えはおそらく「スタック上」です。
また、参照型であるメソッドローカル変数の場合でも、変数(つまり、オブジェクトではなく参照)がスタックに存在することにも注意してください(存在しない場合を除いて、まったく同じルール)。
上記では、説明をIL用語で何が起こるか、つまりC#コンパイラが何をするかに限定していることに注意してください。ハンスは、JITがILを見ると、ILに対してやりたいことを何でもできると言っているのはまったく正しいです。