1

我不是任何类型的 IL 大师,我只是有时使用它来检查编译器对我编写的代码的影响。我一直想知道的一件事是为什么.maxstack有时会获得它所获得的价值。考虑以下类:

public class Sample
{
    public void SomeMethod(){}
}

然后,我有一个这样的程序:

private static void Main(string[] args)
{
    Sample sample = new Sample();
    sample.SomeMethod();      
}

上面的代码给出了以下 IL(发布编译):

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       13 (0xd)
  .maxstack  1
  .locals init ([0] class ConsoleApplication1.Sample sample)
  IL_0000:  newobj     instance void ConsoleApplication1.Sample::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  callvirt   instance void ConsoleApplication1.Sample::SomeMethod()
  IL_000c:  ret
} // end of method Program::Main

现在,如果我将程序代码更改为:

private static void Main(string[] args)
{
    new Sample().SomeMethod();  
}

...它会产生以下 IL 代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       11 (0xb)
  .maxstack  8
  IL_0000:  newobj     instance void ConsoleApplication1.Sample::.ctor()
  IL_0005:  call       instance void ConsoleApplication1.Sample::SomeMethod()
  IL_000a:  ret
} // end of method Program::Main

第二个 IL 代码较短,这是意料之中的。但是让我有点好奇的是,为什么.maxstack我在第二个代码示例中是 8,而在第一个示例中是 1?为什么第二个代码会导致系统为操作保留更大的堆栈?

4

1 回答 1

4

方法头的二进制表示具有“小格式”和“胖格式”。tiny header占用的字节更少,只要满足以下条件就可以使用:

  • 最大堆栈 <= 8
  • 无异常处理
  • 没有局部变量
  • 代码大小 < 64 字节

您的更改允许编译器使用这种形式,并且当遇到一个很小的标头时,它总是假定使用最大堆栈 8。

参考是 ECMA-335 §25.4.2

附带说明我特别感兴趣的是,在发布版本中(根据您在 OP 中的注释),它们生成了不同的代码,其中缩短的形式更小更快。您使用的是哪个版本的 C#?我希望以后的版本能够利用这个明显的优化:

  • 可以在不改变语义的情况下删除局部变量。
  • 当局部变量不存在时,编译器知道该值的具体类型正是Sample,因此即使SomeMethod是虚拟的,它也可以直接用call指令调用它。
于 2009-12-15T08:54:11.347 回答