执行堆栈和线程堆栈以及局部变量列表是否指向不同的堆栈?

本文关键字:堆栈 是否 列表 线程 局部变量 执行 | 更新日期: 2023-09-27 18:14:01

值类型可能存储在线程的堆栈中,而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指的是哪个堆栈的方法?是局部变量列表的大小吗?或者推入执行堆栈的额外存储的最大大小?

在我看来,执行堆栈是正确的堆栈,因为它只支持pushpop。本地变量列表支持从索引到load和从索引到store。这和堆栈有什么关系呢?

更新:

ECMA 335 I.12.3机器状态清楚地回答了我的问题。

执行堆栈和线程堆栈以及局部变量列表是否指向不同的堆栈?

Lasse的回答很好;我只是想强调他的几个观点。

首先将c#翻译成等效的IL程序,然后将IL程序翻译成等效的机器码程序。IL语言有一个评估堆栈的概念;这纯粹是IL语言中的一个概念。所需要做的就是抖动将IL 转换成具有相同最终结果的机器码。没有任何要求抖动实际上使用"真实"堆栈,仅仅因为IL程序被编写为使用"计算堆栈"。

基本上,IL是一种假定极其简化的处理器的语言;在IL中没有"寄存器",只有堆栈。在一个真正的处理器中,当然有寄存器,所以IL的机器码翻译当然不会盲目地遵循它对求值堆栈的使用;那太愚蠢了。

"最大栈"是指IL语言的"抽象"栈;它与机器码中实际线程的堆栈没有特别的连接。

我已经写了很多关于"栈"的含义和为什么我们使用IL的文章;如果你对这个话题感兴趣,你可以看看:

  • 栈是一个实现细节,第一部分

  • 栈是一个实现细节,第二部分

  • 关于值类型的真相

  • 为什么IL ?

  • 为什么要有堆栈?

局部变量通常存储在堆栈中。实际情况是,在方法开始时,堆栈上保留了一些字节,而局部变量位于这些字节中。在方法返回之前,保留的堆栈空间为"未保留",因此变量将从堆栈中删除。

"局部变量列表"是那些存在于保留空间中的变量。

这样做的好处是,一个方法可以调用自己,而不必担心在前一个方法调用中绊倒变量,当内部方法调用返回时,可能仍然需要绊倒变量。

然而,这里也有别的东西在起作用。你指的是IL代码,这是为虚拟计算机编写的"机器代码"。不是在VMware或类似系统下运行的虚拟计算机,而是实际上并不存在的虚拟计算机。没有可用的CPU可以运行IL代码,就像你在问题中发布的那样,未经修改。

相反,在第一次执行方法之前,JITter会介入并将IL代码转换为实际的二进制机器码,这些机器码将在实际的CPU上运行,通常是x86兼容的指令。

最终的二进制代码可能实际上不使用堆栈来保存局部变量,它可能使用CPU寄存器。虚拟机的目标是使用堆栈作为寄存器,x86兼容的cpu有多个可用的寄存器。

因此,您发布的代码可能会被翻译成使用这些寄存器而不是堆栈的二进制代码,因为这些寄存器比访问内存要快。