回答:
わかりました、これをもっと明確にできるか見てみましょう。
まず、Ashは正しいです。問題は、値型変数がどこに割り当てられるかではありません。これは別の質問です。答えは単に「山積み」ではありません。それよりも複雑です(C#2でさらに複雑になっています)。私が持っているトピックに関する記事を、要求された場合は、それを拡張しますが、ただでの契約をしましょう演算子。new
第二に、これらすべては本当にあなたが話しているレベルに依存します。コンパイラーが作成するILの観点から、コンパイラーがソースコードをどのように処理するかを調べています。JITコンパイラーがかなりの「論理」割り当てを最適化するという点で賢明なことを行うことは、可能です。
第三に、私はジェネリックを無視しています。主に私が実際に答えを知らないため、そして部分的には複雑すぎるためです。
最後に、これはすべて、現在の実装についてのものです。C#仕様はこれの多くを指定していません-それは事実上、実装の詳細です。マネージコードの開発者は本当に気にする必要がないと信じている人もいます。どこまで行けるかはわかりませんが、実際にはすべてのローカル変数がヒープ上に存在する世界を想像する価値はあります。
new
値型の演算子には2つの異なる状況があります。パラメーターなしのコンストラクター(例new Guid()
)またはパラメーター付きのコンストラクター(例)を呼び出すことができますnew Guid(someString)
。これらは著しく異なるILを生成します。理由を理解するには、C#とCLIの仕様を比較する必要があります。C#によると、すべての値の型にはパラメーターのないコンストラクターがあります。CLI仕様によると、パラメーターのないコンストラクターを持つ値タイプはありません。(しばらくの間リフレクションを使用して値型のコンストラクターをフェッチします。パラメーターのないコンストラクターは見つかりません。)
C#が「値をゼロで初期化する」ことをコンストラクターとして扱うことは理にかなっています。言語を一貫させるためです。つまりnew(...)
、常にコンストラクターを呼び出すと考えることができます。呼び出す実際のコードはなく、タイプ固有のコードもないので、CLIがそれを別の方法で考えることは理にかなっています。
また、初期化した後の値をどうするかにも違いがあります。使用されるIL
Guid localVariable = new Guid(someString);
使用されるILとは異なります。
myInstanceOrStaticVariable = new Guid(someString);
さらに、値が中間値として使用される場合(メソッド呼び出しへの引数など)、状況は少し異なります。これらの違いをすべて示すために、短いテストプログラムを次に示します。これは、静的変数とインスタンス変数の違いを示していない:ILは、間で異なるだろうstfld
とstsfld
、それがすべてです。
using System;
public class Test
{
static Guid field;
static void Main() {}
static void MethodTakingGuid(Guid guid) {}
static void ParameterisedCtorAssignToField()
{
field = new Guid("");
}
static void ParameterisedCtorAssignToLocal()
{
Guid local = new Guid("");
// Force the value to be used
local.ToString();
}
static void ParameterisedCtorCallMethod()
{
MethodTakingGuid(new Guid(""));
}
static void ParameterlessCtorAssignToField()
{
field = new Guid();
}
static void ParameterlessCtorAssignToLocal()
{
Guid local = new Guid();
// Force the value to be used
local.ToString();
}
static void ParameterlessCtorCallMethod()
{
MethodTakingGuid(new Guid());
}
}
これは、無関係なビット(nopsなど)を除いたクラスのILです。
.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
// Removed Test's constructor, Main, and MethodTakingGuid.
.method private hidebysig static void ParameterisedCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
L_0010: ret
}
.method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
{
.maxstack 2
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: ldstr ""
L_0008: call instance void [mscorlib]System.Guid::.ctor(string)
// Removed ToString() call
L_001c: ret
}
.method private hidebysig static void ParameterisedCtorCallMethod() cil managed
{
.maxstack 8
L_0001: ldstr ""
L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0011: ret
}
.method private hidebysig static void ParameterlessCtorAssignToField() cil managed
{
.maxstack 8
L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
L_0006: initobj [mscorlib]System.Guid
L_000c: ret
}
.method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
// Removed ToString() call
L_0017: ret
}
.method private hidebysig static void ParameterlessCtorCallMethod() cil managed
{
.maxstack 1
.locals init ([0] valuetype [mscorlib]System.Guid guid)
L_0001: ldloca.s guid
L_0003: initobj [mscorlib]System.Guid
L_0009: ldloc.0
L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
L_0010: ret
}
.field private static valuetype [mscorlib]System.Guid field
}
ご覧のとおり、コンストラクターの呼び出しにはさまざまな命令が使用されています。
newobj
:スタックに値を割り当て、パラメーター化されたコンストラクターを呼び出します。中間値に使用されます。たとえば、フィールドへの割り当てやメソッドの引数として使用されます。call instance
:既に割り当てられているストレージの場所を使用します(スタック上かどうかに関係なく)。これは、上記のコードでローカル変数に割り当てるために使用されます。同じローカル変数に複数のnew
呼び出しを使用して値が複数回割り当てられている場合、古い値の上にデータを初期化するだけで、毎回より多くのスタックスペースを割り当てません。initobj
:既に割り当てられている保存場所を使用し、データを消去するだけです。これは、ローカル変数に割り当てるものを含め、すべてのパラメーターなしのコンストラクター呼び出しに使用されます。メソッド呼び出しでは、中間ローカル変数が効果的に導入され、その値はによってワイプされinitobj
ます。これがトピックがいかに複雑であるかを示していると同時に、少しだけ光を当てているといいのですが。では、いくつかの概念の感覚、すべての呼び出しnew
スタック上の割り当てスペース-しかし、我々が見てきたように、実際にも、ILレベルでは何が起こるかではありません。特定のケースを強調したいと思います。この方法を取る:
void HowManyStackAllocations()
{
Guid guid = new Guid();
// [...] Use guid
guid = new Guid(someBytes);
// [...] Use guid
guid = new Guid(someString);
// [...] Use guid
}
その「論理的」には4つのスタック割り当てがあります。1つは変数用で、もう1つは3つのnew
呼び出しごとです。しかし、実際には(その特定のコードの場合)スタックは1回だけ割り当てられ、同じストレージの場所が再利用されます。
編集:明確にするために、これは一部の場合にのみ当てはまります...特に、コンストラクターが例外をスローしたguid
場合、の値は表示されませんGuid
。そのため、C#コンパイラーは同じスタックスロットを再利用できます。詳細と適用されないケースについては、Eric Lippertの値型構築に関するブログ投稿を参照してください。
この回答を書くことで多くのことを学びました-不明な点がある場合は説明を求めてください!
List<Guid>
と、それにそれらの3を追加しますか?それは3つの割り当て(同じIL)でしょうか?しかし、それらはどこか魔法のように保たれています
guid
が半分だけ上書きされたかどうかは関係ありません。
構造体のフィールドを含むメモリは、状況に応じてスタックまたはヒープに割り当てることができます。struct-type変数がローカル変数またはパラメーターであり、匿名デリゲートまたはイテレータークラスによってキャプチャされていない場合は、スタックに割り当てられます。変数がクラスの一部である場合、その変数はクラス内のヒープに割り当てられます。
構造体がヒープに割り当てられている場合、メモリを割り当てるためにnew演算子を呼び出す必要は実際にはありません。唯一の目的は、コンストラクターの内容に従ってフィールド値を設定することです。コンストラクターが呼び出されない場合、すべてのフィールドはデフォルト値(0またはnull)を取得します。
スタックに割り当てられた構造体についても同様ですが、C#では、使用する前にすべてのローカル変数を何らかの値に設定する必要があるため、カスタムコンストラクターまたはデフォルトコンストラクター(パラメーターをとらないコンストラクターを常に使用できる)を呼び出す必要があります。構造体)。
コンパクトに言えば、newは構造体の誤称であり、newを呼び出すとコンストラクタが呼び出されます。構造体の唯一の保存場所は、定義されている場所です。
メンバー変数の場合は、定義されているものに直接格納されます。ローカル変数またはパラメーターの場合は、スタックに格納されます。
これは、構造体が完全に格納されている場所に参照があるクラスと対照的ですが、参照はヒープ上のどこかを指します。(メンバー内、ローカル/スタック上のパラメーター)
クラスと構造体の間に実際の違いがないC ++を少し調べると役立つ場合があります。(言語には同様の名前がありますが、デフォルトのアクセシビリティを参照するだけです)newを呼び出すと、ヒープの場所へのポインターが取得されますが、ポインター以外の参照がある場合は、スタックに直接格納されます。他のオブジェクト内では、alaはC#の構造体です。
すべての値型と同様に、構造体は常に宣言された場所に移動します。
構造体をいつ使用するかの詳細については、こちらの質問を参照してください。そしてこの質問はここに、構造体のいくつかの詳細については、で。
編集:私は、彼らがmistankelyと答えていた常にスタックに行きます。これは誤りです。
私はおそらくここで何かが足りないのですが、なぜ割り当てを気にするのですか?
値の型は値によって渡される;)なので、定義されている場所とは異なるスコープで変更することはできません。値を変更できるようにするには、[ref]キーワードを追加する必要があります。
参照型は参照によって渡され、変更できます。
もちろん、最も人気のある不変の参照型文字列があります。
配列のレイアウト/初期化:値のタイプ->ゼロメモリ[name、zip] [name、zip]参照タイプ->ゼロメモリ-> null [ref] [ref]
A class
またはstruct
宣言は、実行時にインスタンスやオブジェクトを作成するために使用される設計図のようなものです。class
またはstruct
呼び出されるPerson を定義する場合、Personはタイプの名前です。Person型の変数pを宣言して初期化する場合、pはPersonのオブジェクトまたはインスタンスと呼ばれます。同じPersonタイプの複数のインスタンスを作成することができ、各インスタンスはその中に異なる値を持つことができますproperties
とfields
。
あ class
は参照型です。のオブジェクトがclass
が作成されると、オブジェクトが割り当てられている変数は、そのメモリへの参照のみを保持します。オブジェクト参照が新しい変数に割り当てられると、新しい変数は元のオブジェクトを参照します。1つの変数を介して行われた変更は、両方とも同じデータを参照するため、他の変数に反映されます。
A struct
は値タイプです。a struct
が作成されると、struct
が割り当てられている変数は、構造体の実際のデータを保持します。ときstruct
新しい変数に代入され、それがコピーされます。したがって、新しい変数と元の変数には、同じデータの2つの別々のコピーが含まれています。1つのコピーに加えられた変更は、他のコピーには影響しません。
一般に、classes
より複雑な動作、またはclass
オブジェクトの作成後に変更することを目的としたデータをモデル化するために使用されます。Structs
struct
作成後に変更することを意図していない主なデータを含む小さなデータ構造に最適です。
構造体はスタックに割り当てられます。ここに役立つ説明があります:
さらに、.NET内でインスタンス化されたクラスは、ヒープまたは.NETの予約済みメモリ空間にメモリを割り当てます。一方、構造体は、スタックへの割り当てが原因で、インスタンス化時に効率が向上します。さらに、構造体内のパラメーターの受け渡しは値によって行われることに注意してください。