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

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

我也读过Eric Lippert的文章 。

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

谢谢

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

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

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

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

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

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

后三个子弹是它变得复杂的地方,为什么你需要查看机器代码来找出它们的存储位置。 它具有高度的实现特性,抖动类型很重要。 并且您是否已启用抖动优化器会受到很大影响。 在这里做出正确的选择对于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位精度。 发生此溢出的不可预测性(加上x64抖动的不同策略)会产生浮点结果,如果计算失去大量有效数字,则会产生微小变化,从而在计算结果中产生较大差异。