值类型变量是否违反堆栈的LIFO性质

时间:2015-09-03 05:07:05

标签: c# variables memory stack allocation

我已为值类型分配一个值,例如

int i=0;
int j=1;

由于int是一个值类型变量,因此内存在堆栈中分配如下(将I和j的值推入堆栈):

|_|<-- stack top
|1|<--j value
|0|<--i value

我对这个分配几乎没有疑问:

  • 如果我在这个时间执行i+=1,那么堆栈分配的变化是什么?
  • 如何在不弹出i
  • 的情况下弹出j的值
  • 如果我重新分配i,则该值将存储在堆栈顶部,此时会发生以前分配的值:

3 个答案:

答案 0 :(得分:2)

你的心理模型严重受损,你的字面意思是“堆叠”。仅当一个方法调用另一个方法时,处理器堆栈的行为类似于堆栈。在方法中,参数和局部变量存储在stack frame中。它可以自由寻址,不会推或弹。始终作为基址指针寄存器或堆栈指针寄存器的偏移量。 EBP采用32位代码,RSP采用64位代码。堆栈帧的大小取决于局部变量的数量。简单地通过按帧大小递减堆栈指针来“分配”它。并通过恢复堆栈指针“破坏”。

如果您将其建模为C#Stack<object[]>数据结构,则会更近。

Wikipedia article应该有助于澄清这个概念。

答案 1 :(得分:0)

您需要考虑以下信息:

  1. C#是一种及时编译的语言,.NET CLR负责在程序执行期间从.NET代码生成机器代码。
  2. 现代CPU有一个集中使用的寄存器集,以避免每次操作都需要堆叠。
  3. 因此,在您的代码中, .NET CLR很可能将i和j变量放入寄存器,并且很可能会安排代码,以便只有在没有更多寄存器可供使用时才临时变量将被放置在堆栈上。

    然而,该堆栈不是简单的LIFO结构。可以使用EBP和ESP寄存器作为基本偏移来访问堆栈变量。

答案 2 :(得分:0)

创建和读取局部变量时,不会推送和弹出局部变量。

在方法的开头是创建堆栈框架的代码,以便为局部变量腾出空间。这是通过移动堆栈指针在堆栈上创建该方法可以使用的内存区域来完成的。

每个局部变量从一开始就在堆栈帧中具有固定位置,即使变量的范围小于该方法。堆栈帧在执行方法期间不会改变。

|                        |
| More stack data        |
|                        |
+------------------------+
| Method return address  |
+------------------------+
| i                      | Stack frame
| j                      |
+------------------------+
<--- stack pointer

通常bp寄存器设置为指向堆栈帧,因此[bp + 0]访问i[bp + 4]访问j

在方法结束时,有代码通过将堆栈指针放回来移除堆栈帧,以便接下来弹出返回地址。

(请注意,这是一个实现细节,JIT编译器可能会使用一个寄存器来存储变量而不是堆栈框架中的空格,当闭包进入图片时它会改变一些东西,但它可以作为一个了解局部变量如何工作的起点。)