这里有一些 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
我不明白为什么发出 newobj/stobj 而不是简单的 call .ctor ?为了让它更神秘,由 jit-compiler 在 32 位模式下对一个 ctor 调用进行了优化的 newobj+stobj,但在 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) 形式的对象创建表达式的运行时处理,其中 T 是类类型或结构类型,A 是可选的参数列表,包括以下步骤:
...
如果 T 是结构类型:
T 类型的实例是通过分配一个临时局部变量来创建的。由于结构类型的实例构造函数需要明确地为正在创建的实例的每个字段分配一个值,因此不需要初始化临时变量。
根据函数成员调用规则(第 7.5.4 节)调用实例构造函数。对新分配的实例的引用会自动传递给实例构造函数,并且可以从该构造函数中访问实例作为 this。
我想强调“分配一个临时局部变量”。在我的理解中,newobj 指令假设在堆上创建对象......
在这种情况下,对象创建与使用方式的相关性让我感到沮丧,因为 foo1 和 foo2 对我来说看起来相同。