C# IL&;net中的堆栈实现?

C# IL&;net中的堆栈实现?,c#,.net,compiler-construction,il,C#,.net,Compiler Construction,Il,我编写了一个简单的程序来检查IL是如何工作的: void Main() { int a=5; int b=6; if (a<b) Console.Write("333"); Console.ReadLine(); } 我试图了解实施的效率: 在第1行(IL代码),它将值5推送到堆栈上(4个字节,即int32) 在第2行(IL代码),它从堆栈中弹出到一个局部变量中 接下来的两行也是如此 然后,它将这些局部变量加载到堆栈中,然后计算bge.s 问题1 他为什么要将局部变量加载到

我编写了一个简单的程序来检查IL是如何工作的:

void Main()
{

 int a=5;
 int b=6;
 if (a<b) Console.Write("333");
 Console.ReadLine();
}
我试图了解实施的效率:

  • 在第1行(IL代码),它将值5推送到堆栈上(4个字节,即int32)

  • 在第2行(IL代码),它从堆栈中弹出到一个局部变量中

接下来的两行也是如此

然后,它将这些局部变量加载到堆栈中,然后计算
bge.s

问题1

他为什么要将局部变量加载到堆栈中?这些值已在堆栈中。但是他把它们放在一个局部变量中。这不是浪费吗

我是说,为什么代码不能是这样的:

IL_0000:  ldc.i4.5
IL_0001:  ldc.i4.6    
IL_0002:  bge.s       IL_0004
IL_0003:  ldstr       "333"
IL_0004:  call        System.Console.Write
IL_0005:  call        System.Console.ReadLine
我的代码示例只有5行代码。5000万行代码怎么样?IL将发出大量额外的代码

问题2

查看代码地址:

  • IL_0009地址在哪里?它不应该是连续的吗

p、 我可以轻松回答第二个问题。指令长度可变。例如,
ldstr“333”
ldstr
(地址
8
)的操作码和表示字符串的数据(用户字符串表中的字符串引用)组成

与之后的
call
语句类似,您需要
call
操作码本身以及有关要调用的函数的信息

将小值(如4或6)推送到堆栈上的指令没有额外数据的原因是,这些值被编码到操作码本身中

有关说明和编码,请参阅

关于第一个问题,您可能想看看,其中说明:

/optimize标志不会改变大量的发射和生成逻辑。我们总是试图生成直接的、可验证的代码,然后在抖动生成真正的机器代码时依靠抖动来完成繁重的优化工作


在这个层次上,关于IL效率的推理是没有意义的

JIT将完全消除堆栈,将所有堆栈操作转换为中间三地址代码(并进一步转换为SSA)。由于IL从不被解释,堆栈操作不应该是有效的和优化的

例如,请参见开源Mono实现

他为什么要将局部变量加载到堆栈中?这些值已在堆栈中。但是他把它们放在一个局部变量中。这不是浪费吗

浪费什么?您必须记住,IL(通常)不是按原样执行的,它是由执行大多数优化的JIT编译器再次编译的。使用“中间语言”的要点之一是可以在一个地方实现优化:JIT编译器和每种语言(C#,VB.NET,F#,…)都不必重新实现它们。Eric Lippert在他的文章中对此进行了解释

IL_0009地址在哪里?它不应该是连续的吗

让我们看一下
ldstr
指令的规范(来自):

三、 4.16
ldstr
–加载文本字符串

格式:72[…]

ldstr
指令推送一个新的string对象,该对象将元数据中存储的文本表示为string(字符串文本)

上面对元数据的引用,意味着指令的字节
72
后跟一个元数据标记,该标记指向一个包含字符串的表。这种代币有多大?同一文件第III.1.9节:

许多CIL指令后面都有一个“元数据令牌”。这是一个4字节的值,用于指定元数据表中的一行[…]


因此,在您的例子中,指令的字节
72
位于地址0008,而令牌(在本例中为0x7000001,其中0x70字节表示用户字符串表)位于地址0009到000C。

给出关于“额外代码”的所有讨论的最终答案

C#编译器读取
inta=5并将其转换为:

ldc.i4.5
stloc.0
然后它转到下一行并读取
intb=6并转换为:

ldc.i4.6
stloc.1
然后用if语句读取下一行,依此类推

当从C#编译到IL时,它逐行读取并将该行转换为IL,而不是在查看其他行时将该行转换为IL

在这个阶段,为了优化IL并删除“额外代码”(您称之为“额外代码”),C#编译器必须检查所有IL代码,构建它的树表示,删除所有不需要的节点,然后再次将其作为IL写入。这不是C#编译器应该做的事情,因为从IL到机器语言时,这将由JIT编译器完成

因此,您看到的额外代码不是额外代码,它是C编译器从您的C代码中读取的语句的一部分,当JIT编译器将代码编译为本机可执行文件时,它将被删除

这是一个关于C代码如何翻译的高级解释,因为我认为您没有在编译器构造或类似的东西中使用过任何类。
如果你想知道更多,互联网上有很多书和网页可供阅读。

这是调试还是优化编译器?@spender release mode+optimize on对于问题1,它完全是在编译你的代码,但我认为真正的问题是,为什么优化器不去掉你的局部变量“a”和“b”,而只是将其编译成
If(5<6).
或者,既然它总是
真的,为什么不一起去掉条件呢?
?也许优化器就没有那么好。你说推理没有意义是什么意思。我想看看和学习事情是如何运作的。我想
ldc.i4.6
stloc.1