任何在.NET程序执行时查看变量存储位置的工具?它是在堆栈还是堆上?

Ran*_*tha 25 .net c# vb.net memory-management

从很久以前我想知道确切地存储变量(值类型或引用类型)的位置.它会在堆栈还是堆上?

我也读过Eric Lippert的文章.

出于好奇,我想要的是交叉验证我所理解的内容.任何工具都存在相同的?或者以任何方式我会知道,当.NET程序被执行时,哪些变量存储在堆栈中?哪个存储在堆上?

谢谢

Han*_*ant 69

考虑存储被堆栈和堆分割是一种方便的抽象,可以很好地为您服务.但它更复杂,.NET程序中的变量有6个不同的存储位置.

这里选择的工具是调试器,它可以准确显示变量存储的位置.这需要深入了解机器代码的工作原理.使用Debug + Windows + Disassembly查看机器代码.查看程序的Release版本并更改允许代码优化的设置也很重要,即使在调试时也是如此.工具+选项,调试,常规,取消选中"抑制模块加载时的JIT优化"选项.您现在将看到机器代码在用户机器上的执行方式.

你必须事先了解的事情才能理解这一切:

  • 引用类型的对象存储在GC堆上.存储引用的变量与值类型值具有相同类型的存储选择.

  • 值类型值或对象引用有六个可能的存储位置:

    • 如果变量是引用类型的成员,它们将存储在GC堆上
    • 如果变量声明为static,它们将存储在AppDomain的加载程序堆中
    • 如果变量是[ThreadStatic],它们存储在线程本地存储中
    • 如果变量是方法参数或局部变量,则它们可以存储在堆栈帧中
    • 如果变量是方法参数或局部变量,它们可以存储在CPU寄存器中
    • 特定于x86抖动,可以在FPU堆栈中存储Single或Double类型的变量.

后三个子弹是它变得复杂的地方,为什么你需要查看机器代码来找出它们的存储位置.它具有高度的实现特性,抖动类型很重要.并且您是否已启用抖动优化器会对此产生很大影响.在这里做出正确的选择对于perf非常重要.粗略轮廓(跳过ARM抖动):

  • 前两个方法参数存储在x86抖动的CPU寄存器中,包括实例方法的值.x64抖动使用4个寄存器.浮点处理器寄存器用于在x86上传递Single和Double类型的变量,在x64上传递XMM寄存器

  • 如果CPU寄存器适合,则使用EAX或RAX寄存器返回函数返回值,如果它是浮点值,则返回ST0.如果它不适合,那么调用者在堆栈帧上为值保留空间并传递指针

  • 抖动优化器寻找在CPU寄存器中存储局部变量的机会.如果强制执行此操作,它可能会将寄存器溢出回堆栈帧,因为它已超出寄存器.

这些实现细节有许多可观察到的副作用:

  • 获取存储在cpu寄存器中的局部变量会使代码难以调试.调试器对存储位置知之甚少.这是Debug构建存在的主要原因,它抑制了优化,因此您可以轻松地检查局部变量,调试器确实知道用于变量的堆栈帧槽
  • 您无法检查方法的返回值,在调试时会带来很大的不便.调试器对抖动选择的存储位置知之甚少,无法可靠地找到该值.编辑:修复VS2013
  • 由于变量被优化以存储在cpu寄存器中,因此很难调试线程问题.在循环或if()语句中测试值会产生寄存器中值的副本,而不是存储在内存中的值.特别是x86抖动的问题以及volatile关键字的原因,volatile关键字抑制了这种优化
  • 您可以初始化指向局部变量的指针,而无需固定它.与存储在GC堆中的可能由垃圾收集移动并因此需要固定的变量不同,局部变量具有固定的存储地址,该地址在整个方法体中都是有效的
  • 为堆栈帧分配的空间量由抖动确定.但是,可以自己分配一个块,C#stackalloc关键字支持它.这是您可以直接分配的最快内存
  • 存储在FPU寄存器中的浮点值会导致浮点精度问题.当它存储在FPU中时,以80位精度存储值.但是当它溢出到内存时,会被截断为32位或64位精度.当此发生溢出的不可预测性(加上64抖动的不同的策略)产生浮点结果可以是具有小的变化产生在计算结果大的差异如果计算失去了大量的显著位数不同.