值类型可以存储在线程的堆栈中,IL在执行堆栈中运行(抽象概念)。
int y=0;
int a=0;
int b=0;
int x = y + (a - b);
IL_0001: ldc.i4.0
IL_0002: stloc.0 // y
IL_0003: ldc.i4.0
IL_0004: stloc.1 // a
IL_0005: ldc.i4.0
IL_0006: stloc.2 // b
IL_0007: ldloc.0 // y
IL_0008: ldloc.1 // a
IL_0009: ldloc.2 // b
IL_000A: sub
IL_000B: add
IL_000C: stloc.3 // x
现在stloc.0
弹出执行堆栈中的值并存储到本地变量列表中。因此,局部变量列表必须存储在执行堆栈以外的其他空间中。什么是局部变量列表?它是一个线程的堆栈吗?
此外,.maxstack = 3
引用哪个堆栈的方法?它是局部变量列表的大小吗?或推入执行堆栈的额外存储的最大大小?
在我看来,执行堆栈是正确的堆栈,因为它只支持push
和pop
。局部变量列表支持从索引load
和store
到索引。这与堆栈有什么关系?
ECMA 335 I.12.3机器状态明确回答了我的问题。
答案 0 :(得分:7)
拉斯的回答非常好;我只想强调他的一些观点。
首先,将C#转换为等效的IL程序,然后将IL程序转换为等效的机器代码程序。 IL语言具有评估堆栈的概念; 这纯粹是IL语言中的一个概念。所需要的只是抖动将IL 转换为具有相同最终结果的机器代码。没有任何要求抖动实际上使用“真正的”堆栈只是因为IL程序被编写为使用“评估堆栈”。
基本上,IL是一种假定处理器非常简化的语言; IL中没有“寄存器”,只有堆栈。在一个真正的处理器当然有寄存器,因此当然IL的机器代码翻译不会盲目地遵循它对评估堆栈的使用;那会很傻。
“最大堆栈”是指IL语言的“抽象”堆栈;它与机器代码中的实际线程堆栈没有特定的连接。
我写了很多关于“堆栈”含义的文章以及我们使用IL的原因;如果这个主题让你感兴趣,你可能想看看它们:
答案 1 :(得分:5)
本地变量通常存储在堆栈中。会发生的是,在方法开始时,堆栈上保留了许多字节,并且局部变量位于这些字节内。在方法返回之前,保留的堆栈空间是“未保留的”,因此从堆栈中删除变量。
“局部变量列表”是那些存在于保留空间内的变量。
这样做的好处是方法可以调用自身而不必担心在前一个方法调用中跳过变量,这可能在内部方法调用返回时仍然需要。
然而,这里还有其他一些东西在起作用。您指的是IL代码,它是为虚拟计算机编写的“机器代码”。不是在VMware或类似的情况下运行的虚拟计算机,而是真正不存在的虚拟计算机。没有可用的CPU将运行IL代码,就像您在问题中发布的那样,未经修改。
相反,在第一次执行方法之前,JITter会介入并将IL代码转换为将在实际CPU上运行的实际二进制机器代码,通常是x86兼容指令。
最终的二进制代码实际上可能不会使用堆栈来保存局部变量,它可能使用CPU寄存器。 IL所针对的虚拟机使用堆栈作为寄存器,x86兼容的cpus有多个寄存器可供使用。
因此,您发布的代码可能会被转换为使用这些寄存器而不是堆栈的二进制代码,因为这些寄存器比访问内存更快。