Any tool to see where variables are stored while a .NET program is executing? Is it on the stack or heap?

前端 未结 1 827
执笔经年
执笔经年 2020-12-02 09:22

From long time back I wanted to know where exactly a variable (be it value type or reference type) will get stored. Will it be on stack or heap?

I have read Eric Lip

相关标签:
1条回答
  • 2020-12-02 09:28

    Thinking of storage being divided by stack and heap is a convenient abstraction that will serve you well. But it is much more convoluted, there are 6 distinct storage locations for variables in a .NET program.

    The tool of choice here is the debugger, it can show you exactly where variables are getting stored. That does require insight into how machine code works. Use Debug + Windows + Disassembly to see the machine code. It is also important that you look at the Release build of your program and change a setting that allows the code to be optimized even when you debug it. Tools + Options, Debugging, General, untick the "Suppress JIT optimization on module load" option. You'll now see the machine code the way it will execute on your user's machine.

    Things you have to know up front to make sense of it all:

    • Objects of a reference type are stored on the GC heap. The variable that stores a reference has the same kind of storage choices as value type values.

    • Value type values or object references have six possible storage locations:

      • They are stored on the GC heap if the variable is a member of a reference type
      • They are stored in the AppDomain's loader heap if the variable is declared static
      • They are stored in thread-local storage if the variable is [ThreadStatic]
      • They can be stored in a stack frame if the variable is a method argument or a local variable
      • They can be stored in a CPU register if the variable is a method argument or a local variable
      • Specific to the x86 jitter, a variable of type Single or Double can be stored in the FPU stack.

    The latter three bullets is where it gets complicated and why you need to look at machine code to find out where they are stored. It is highly implementation specific, the kind of jitter matters. And is highly affected by whether or not you've got the jitter optimizer enabled. Making the proper choices here is very important to perf. The rough outline (skipping the ARM jitter):

    • The first two method arguments are stored in CPU registers for the x86 jitter, including the value of this for instance methods. The x64 jitter uses 4 registers. Floating point processor registers are used to pass variables of type Single and Double on x86, XMM registers on x64

    • A function return value is returned in a CPU register, if it fits, using the EAX or RAX register, ST0 if it is a floating point value. If it doesn't fit then the caller reserved space on the stack frame for the value and passed a pointer to it

    • The jitter optimizer looks for opportunities to store local variables in CPU registers. It may spill the register back to the stack frame if it is forced to do so because it is out of registers.

    There are a number of observable side effects of these implementation details:

    • Getting local variables stored in cpu registers makes code difficult to debug. The debugger doesn't know enough about the storage location. This is the primary reason the Debug build exists, it suppresses the optimization so you can easily inspect local variables, the debugger does know the stack frame slot used for the variable
    • You cannot inspect the return value of a method, a significant inconvenience while debugging. The debugger doesn't know enough about the storage location selected by the jitter to reliably find the value. EDIT: fixed in VS2013
    • You can get difficult to debug threading problems due to a variable being optimized to be stored in a cpu register. Testing the value in a loop or if() statement yields the copy of the value in the register, not the value as stored in memory. Particularly an issue with the x86 jitter and the reason for the volatile keyword, a keyword that suppresses this optimization
    • You can initialize a pointer to a local variable without having to pin it. Unlike variables stored in the GC heap that may be moved by a garbage collection and thus require pinning, local variables have a fixed storage address that's valid throughout the method body
    • The amount of space allocated for a stack frame is determined by the jitter. It is however possible to allocate a chunk yourself, the C# stackalloc keyword supports it. This is the fastest memory you can allocate directly
    • Having floating point values stored in an FPU register causes floating point accuracy problems. While it is stored in the FPU, a value is stored with 80 bit precision. But when it is spilled to memory, it is truncated to 32 or 64 bit precision. The unpredictability of when this spill occurs (plus the different strategy of the x64 jitter) produces floating point results that can be different with small changes producing large differences in the calculation result if the calculation loses a lot of significant digits.
    0 讨论(0)
提交回复
热议问题