おそらく不可能です:
が呼び出されるまでに、b
a によって使用されるスタック内のスペースarg1
(IL スタック、つまり、スタックに入れられることさえなかった可能性がありますが、呼び出しで登録されていた可能性があります) は、 によって引き続き使用されることが保証されていませんarg1
。
拡張により、arg1
が参照型である場合、それが参照するオブジェクトは、 の呼び出し後に使用されない場合、ガベージ コレクションされていないことが保証されませんb
。
編集:
あなたのコメントは、あなたがこれを理解していないことを示唆しており、まだ可能であると考えているため、もう少し詳しく説明します。
ジッターによって使用される呼び出し規則は、関連する標準の仕様では指定されていないため、実装者は自由に改善できます。実際、32 ビット バージョンと 64 ビット バージョン、およびリリースによって異なります。
ただし、MS 関係者の記事では、使用されている規則が__fastcall規則に似ていることが示唆されています。への呼び出しa
では、コードが実行されているコアのarg1
ECX レジスター* とarg2
EDX レジスター (32 ビット x86、amd64 でさらに多くの引数が登録されていると仮定して簡略化しています) に入れられます。arg3
スタックにプッシュされ、実際にメモリに存在します。
この時点では、arg1
とarg2
が存在するメモリ ロケーションはなく、CPU レジスタにのみ存在することに注意してください。
メソッド自体を実行する過程で、必要に応じてレジスタとメモリが使用されます。とb
呼ばれます。
今、 ifa
が必要になるarg1
かarg2
、呼び出す前にそれをプッシュする必要がありますb
。しかし、そうでない場合はそうではありません - そして、この必要性を減らすために物事を並べ替えるかもしれません. 逆に、これらのレジスターは、この時点ですでに別の目的で使用されている可能性があります。ジッターは愚かではないため、スタックにレジスターまたはスロットが必要で、メソッドの残りの部分で使用されていないものが 1 つある場合は、次のようになります。そのスペースを再利用します。(さらに言えば、これより上のレベルでは、C# コンパイラは、生成された IL が使用する仮想スタック内のスロットを再利用します)。
したがって、b
が呼び出されると、arg4
レジスタ ECX に配置され、arg5
EDX に配置さarg6
れ、スタックにプッシュされます。この時点でarg1
andarg2
は存在せず、リサイクルされてトイレットペーパーになった後に本を読むことができないのと同じように、それらが何であったかを知ることはできません.
(興味深いことに、メソッドが同じ位置で同じ引数を使用して別のメソッドを呼び出すことは非常に一般的です。この場合、ECX と EDX はそのままにしておくことができます)。
次に、b
サイズに応じて、その戻り値を EAX レジスター、または EDX:EAX ペア、または EAX が指すメモリに入れ、戻り値をa
そのレジスターに入れる前に、さらに作業を行います。
これは、最適化が行われていないことを前提としています。実際にb
は、まったく呼び出されなかった可能性がありますが、そのコードがインライン化された可能性があります。この場合、値がレジスタ内にあるのかスタックにあるのか - 後者の場合、それらがスタック上にあった場所は、b
の署名とは何の関係もなく、関連する値がa
のへの別の「呼び出し」の場合、またはfromへの別の「呼び出しb
」の場合でさえも異なります。別の、さらに別の別の方法でインライン化されています。たとえば、b
a
a
b
arg4
別の呼び出しによって返された値から直接取得されたものであり、この時点で EAX レジスタにある可能性がありますarg5
が、ECX にあるのは と同じでarg1
ありarg6
、によって使用されているスタック空間の途中にあったためa
です。
もう 1 つの可能性は、への呼び出しb
が削除されたテール コールだったということです。への呼び出しは、スタックにプッシュするのではなく、(またはその他の可能性)b
によってすぐに戻り値が返されるため、使用されている値a
bya
はその場で置き換えられ、戻りアドレスが変更され、 return fromb
が を呼び出したメソッドにジャンプして戻るようa
になり、作業の一部がスキップされます (そして、代わりにスタックをオーバーフローする一部の関数型スタイルのアプローチが機能する程度までメモリ使用量が削減されます)そして実際にうまく機能します)。この場合、 への呼び出し中に、 へb
のパラメータa
は、スタックにあったものも含めて完全になくなっている可能性があります。
この最後のケースを最適化と見なすべきかどうかについては、非常に議論の余地があります。一部の言語は、(スタックをオーバーフローする代わりに) まったく機能する場合でも、優れたパフォーマンスを提供し、そうでなければ恐ろしいパフォーマンスを提供するため、実行に大きく依存しています。
他のあらゆる種類の最適化が存在する可能性があります。他にもあらゆる種類の最適化が必要です。.NET チームまたは Mono チームが、私のコードを高速化したり、メモリの使用量を減らしたりして、それ以外は同じように動作する場合、私が何かをしなくても、文句を言うことはありません!
そしてそれは、最初に C# を書いた人がパラメーターの値を決して変更しなかったことを前提としていますが、これは確かに真実ではありません。次のコードを検討してください。
IEnumerable<T> RepeatedlyInvoke(Func<T> factory, int count)
{
if(count < 0)
throw new ArgumentOutOfRangeException();
while(count-- != 0)
yield return factory();
}
C# コンパイラとジッターが、上記の方法でパラメーターが変更されないことを保証できるほど無駄な方法で設計されていたとしてもcount
、 の呼び出し内から何が既に行われていたかをどのように知ることができますfactory
か? 最初の呼び出しでも違いますが、上記のような奇妙なコードではありません。
つまり、要約すると:
- ジッター: パラメータはしばしば登録されます。x86 では 2 つのポインター、参照、または整数パラメーターがレジスターに配置され、amd64 では 4 つのポインター、参照、または整数パラメーターと 4 つの浮動小数点パラメーターがレジスターに配置されることが期待できます。それらには、それらを読み取る場所がありません。
- ジッター: スタック上のパラメーターはしばしば上書きされます。
- ジッター: 実際の呼び出しはまったくない可能性があるため、パラメーターはどこにでもある可能性があるため、パラメーターを探す場所がありません。
- ジッター: 「呼び出し」は、最後のフレームと同じフレームを再利用している可能性があります。
- コンパイラ: IL は、ローカルのスロットを再利用する場合があります。
- 人間: プログラマーはパラメーター値を変更できます。
そのすべてから、いったい何が何であったかを知ることarg1
ができるのでしょうか?
ここで、ガベージ コレクションの存在を追加します。arg1
これらすべてにもかかわらず、とにかく魔法のように何が何であるかを知ることができると想像してみてください. それがヒープ上のオブジェクトへの参照であった場合、上記のすべてがスタック上でアクティブな参照がこれ以上ないことを意味する場合、これが確実に発生することは明らかであるため、それでも役に立たない可能性があります。 GC が作動すると、オブジェクトが収集された可能性があります。したがって、魔法のように手に入れることができるのは、もはや存在しないものへの参照だけです。実際、ヒープ内の領域が別の何かに使用されている可能性が非常に高く、bang はフレームワーク全体の型安全性全体に行きます!
IL を取得するリフレクションに匹敵するものではありません。
- IL は、特定の時点での単なる状態ではなく、静的です。同様に、お気に入りの本を初めて読んだときの反応を取り戻すよりも、図書館からお気に入りの本を入手する方がはるかに簡単です。
- とにかく、ILはインライン化などの影響を反映していません。呼び出しが実際に使用されるたびにインライン化され、リフレクションを使用して
MethodBody
そのメソッドの を取得した場合、通常はインライン化されているという事実は関係ありません。
プロファイリング、AOP、およびインターセプトに関する他の回答の提案は、あなたが得る限り近いものです。
*実際にthis
は、インスタンス メンバーに対する本当の最初のパラメーターです。これを指摘し続ける必要がないように、すべてが静的であると仮定しましょう。