这里是一些用 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 而不是简单的调用 .ctor ? 更神秘的是,newobj+stobj 在 32 位模式下被 jit-compiler 优化为一个 ctor 调用,但在 64 位模式下它没有...
更新:
为了澄清我的困惑,以下是我的期望。
值类型声明表达式如
Foo foo = new Foo(10)
应该通过
编译调用实例 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 在之前某处声明过
应该编译成这样:
.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 Object creation expressions
...
The run-time processing of an object-creation-expression of the form new T(A), where T is class-type or a struct-type and A is an optional argument-list, consists of the following steps:
...
If T is a struct-type:
An instance of type T is created by allocating a temporary local variable. Since an instance constructor of a struct-type is required to definitely assign a value to each field of the instance being created, no initialization of the temporary variable is necessary.
The instance constructor is invoked according to the rules of function member invocation (§7.5.4). A reference to the newly allocated instance is automatically passed to the instance constructor and the instance can be accessed from within that constructor as this.
我想强调“分配一个临时本地变量”。 在我的理解中,newobj 指令假设在堆上创建对象......
在这种情况下,对象创建与其使用方式的依赖性让我很失望,因为 foo1 和 foo2 对我来说看起来完全相同。
最佳答案
首先,您应该阅读我关于这个主题的文章。它没有解决您的特定场景,但它有一些很好的背景信息:
https://ericlippert.com/2010/10/11/debunking-another-myth-about-value-types/
好的,既然您已经阅读过,您就知道 C# 规范规定构造结构实例具有以下语义:
- 创建一个临时变量来存储结构值,初始化为结构的默认值。
- 将对该临时变量的引用作为构造函数的“this”传递
所以当你说:
Foo foo = new Foo(123);
相当于:
Foo foo;
Foo temp = default(Foo);
Foo.ctor(ref temp, 123); // "this" is a ref to a variable in a struct.
foo1 = temp;
现在,您可能会问,既然我们已经拥有一个变量foo
,为什么还要费力地分配一个临时变量呢?就在那里可能是this
:
Foo foo = default(Foo);
Foo.ctor(ref foo, 123);
这种优化称为复制省略。当 C# 编译器和/或抖动确定使用他们的启发式方法时,他们可以执行复制省略,因为这样做总是不可见。在极少数情况下,复制省略会导致程序发生可观察到的变化,在这种情况下,不得使用优化。例如,假设我们有一对整数结构:
Pair p = default(Pair);
try { p = new Pair(10, 20); } catch {}
Console.WriteLine(p.First);
Console.WriteLine(p.Second);
我们期望p
这是(0, 0)
或 (10, 20)
, 从不 (10, 0)
或 (0, 20)
,即使 ctor 中途抛出。也就是说,要么分配给 p
是完全构建的值,或者没有对 p
进行修改根本。这里不能进行复制省略;我们必须制作一个临时文件,将临时文件传递给 ctor,然后将临时文件复制到 p
.
同样,假设我们有这种精神错乱:
Pair p = default(Pair);
p = new Pair(10, 20, ref p);
Console.WriteLine(p.First);
Console.WriteLine(p.Second);
如果 C# 编译器执行复制省略,则 this
和 ref p
都是 p
的别名,这明显不同于如果 this
是临时的别名! ctor 可以观察到 this
的变化导致对 ref p
的更改如果他们给同一个变量起了别名,但如果他们给不同的变量起了别名就不会观察到。
C# 编译器启发式决定对 foo1
执行复制省略但不是 foo2
在你的程序中。它看到有一个 ref foo2
在你的方法中并决定在那里放弃。它可以进行更复杂的分析以确定它不在这些疯狂的别名情况之一中,但事实并非如此。如果有任何机会,无论多么遥远,都可能存在使省略可见的混叠情况,那么便宜且容易做的事情就是跳过优化。它生成 newobj
编码,让抖动决定是否要进行省略。
至于抖动:64位和32位抖动有完全不同的优化器。显然其中一个决定它可以引入 C# 编译器没有的复制省略,而另一个则没有。
关于c# - 为什么 c# 编译器在某些情况下会发出 newobj/stobj 而不是 'call instance .ctor' 来进行结构初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15207683/