ここにc#のテストプログラムがあります:
using System;
struct Foo {
int x;
public Foo(int x) {
this.x = x;
}
public override string ToString() {
return x.ToString();
}
}
class Program {
static void PrintFoo(ref Foo foo) {
Console.WriteLine(foo);
}
static void Main(string[] args) {
Foo foo1 = new Foo(10);
Foo foo2 = new Foo(20);
Console.WriteLine(foo1);
PrintFoo(ref foo2);
}
}
ここでは、メソッド Main の逆アセンブルされたコンパイル済みバージョンを示します。
.method private hidebysig static void Main (string[] args) cil managed {
// Method begins at RVA 0x2078
// Code size 42 (0x2a)
.maxstack 2
.entrypoint
.locals init (
[0] valuetype Foo foo1,
[1] valuetype Foo foo2
)
IL_0000: ldloca.s foo1
IL_0002: ldc.i4.s 10
IL_0004: call instance void Foo::.ctor(int32)
IL_0009: ldloca.s foo2
IL_000b: ldc.i4.s 20
IL_000d: newobj instance void Foo::.ctor(int32)
IL_0012: stobj Foo
IL_0017: ldloc.0
IL_0018: box Foo
IL_001d: call void [mscorlib]System.Console::WriteLine(object)
IL_0022: ldloca.s foo2
IL_0024: call void Program::PrintFoo(valuetype Foo&)
IL_0029: ret
} // end of method Program::Main
単純な呼び出し .ctor の代わりに newobj/stobj が発行された理由がわかりませんか? さらに不思議なことに、32ビットモードのjit-compilerによって最適化されたnewobj + stobjが1つのctor呼び出しに最適化されましたが、64ビットモードではそうではありません...
アップデート:
私の混乱を明確にするために、以下は私の期待です。
値型宣言式
Foo foo = new Foo(10)
経由でコンパイルする必要があります
call instance void Foo::.ctor(int32)
値型宣言式
Foo foo = default(Foo)
経由でコンパイルする必要があります
initobj Foo
私の意見では、構造式の場合の一時変数、またはデフォルト式のインスタンスは、危険な動作に従うことができないため、ターゲット変数と見なす必要があります
try{
//foo invisible here
...
Foo foo = new Foo(10);
//we never get here, if something goes wrong
}catch(...){
//foo invisible here
}finally{
//foo invisible here
}
のような代入式
foo = new Foo(10); // foo declared somewhere before
次のようにコンパイルする必要があります。
.locals init (
...
valuetype Foo __temp,
...
)
...
ldloca __temp
ldc.i4 10
call instance void Foo::.ctor(int32)
ldloc __temp
stloc foo
...
これは、C#の仕様が何を言っているかを理解する方法です:
7.6.10.1 オブジェクト作成式
...
new T(A) 形式の object-creation-expression の実行時処理 (T はクラス型または構造体型、A はオプションの引数リスト) は、次の手順で構成されます。
...
T が構造体型の場合:
T 型のインスタンスは、一時的なローカル変数を割り当てることによって作成されます。struct-type のインスタンス コンストラクターは、作成されるインスタンスの各フィールドに確実に値を割り当てる必要があるため、一時変数の初期化は必要ありません。
インスタンス コンストラクターは、関数メンバー呼び出しの規則に従って呼び出されます (§7.5.4)。新しく割り当てられたインスタンスへの参照は、インスタンス コンストラクターに自動的に渡され、そのコンストラクター内からインスタンスにアクセスできます。
「一時ローカル変数の割り当て」を強調したい。私の理解では、 newobj 命令はヒープ上にオブジェクトを作成することを前提としています...
この場合、foo1 と foo2 は同じように見えるため、オブジェクト作成の使用方法による依存関係が私を失望させます。