How to access C # stack using CLR?

This may be a very simple question, but I could not find the answer here on SO, and did not know who I asked to answer:

I can write a simple C # method as follows:

private void foo() { int a = 1; int b = 5; } 

If the CIL code (generated by the compiler) is executed using the Common Language Runtime, it will create the following fields on top of the stack, while the executive control is inside the method:

 b = 5 a = 1 

But now I am expanding a method to access a field called "a":

 private void foo() { int a = 1; int b = 5; Console.WriteLine(a); } 

Now the CLR must access a field that is not on top of the stack, but in accordance with the FILO principle (first in last), it must take care of all the fields over the requested fields before accessing it.

What happens to field "b", which is on the stack above the requested field "a"?

It is not possible to delete it, since it can be used by the execution method after that, and what happens to it?

AFAIK, there are only two ways to store a field, stack, or heap. Moving it to a heap does not make much sense, since it will require all the benefits of stacking from the CLR. Does the CLR create something like a second stack?

How it works?

-edit -

Perhaps I did not explain my intentions enough.

If I write a method like this:

 private void foo() { int a = 1; int b = 5; Console.WriteLine(a); Console.WriteLine(b); } 

The CLR first writes 2 fields to the stack and accesses them later, but in the reverse order.

First, he must gain access to field “a,” but to get to it, the CLR must take care of field “b”, which lies above field “a” on the stack. He cannot simply remove field “b” from the stack, since he must access it later.

How it works?

+5
source share
6 answers

Note that when you talk about fields, a and b are called local variables.

Perhaps the following simplified logical presentation may clarify the situation. Before calling Console.WriteLine top of the stack will look something like this:

 |5| // b |1| // a 

Inside Console.WriteLine , an additional stack frame (called value , which receives a copy of the variable a ) is added for its parameter:

 |1| // value = a |5| // b |1| // a 

As soon as Console.WriteLine returns, the top frame is popped up and the stack again becomes:

 |5| // b |1| // a 
+3
source

Variables do not stack separately; the stack contains "frames". Each frame contains all the variables (locales, parameters, etc.) necessary for the current method call. Therefore, in your example, a and b exist side by side in the same frame, and there is no need to delete them. When the foo method exits, the entire stack stack is popped out of the stack, leaving the frame of the calling method at the top.

A wikpedia article may give some enlightenment.

+6
source

The call stack is not a strictly "clean" stack, where you can only interact with the top element. In the call stack, you collect entire function calls and / or entire variable areas, not variables.

For example, if a new function is called, for example foo() , it places its two variables a and b on top of the stack and has full access to them. He (usually) knows nothing below these variables on the stack.

Take a look at this code:

 void foo() { // << Space is allocated on the stack for a and b. // << Anything in this scope has full access to a and b. // << But you cannot (normally) access anything from the // << calling function. var a = 1; var b = 2; if (a == 1) { // << Another variable scope is placed on the stack. // << From here you can access a, b and c. var c = 3; } // << c is removed from the stack. } // << a, b and anything else in foo() is removed from the stack. 
+4
source

You have the wrong mental image about the stack; it only acts as a stack between method calls. Inside the method, the stack frame acts as an array of local variables. There is also nothing special about the managed code stack frame; it works just like the stack frame used in native C or C ++ code.

Local variables have a fixed offset from the EBP register, the pointer to the stack frame. This offset is determined by the JIT compiler.

The specific result of the code that you published is that the optimizer built into the compiler “right at the point in time” simply eliminates local variables that are not used. The variable a in the last example is likely to be in the processor register and will never be on the stack. Standard optimization.

+2
source

When it comes to CLRs, it's best to think of local variables as numbered “slots,” such as mailboxes. Whether the values ​​stored in these “slots” in the frame of the method stack (others cover this concept here), whether they are stored in the CPU registers, or even completely optimized, are jitter details. For more information, see IL Stloc Instruction .

It is better to think that the CLR launches an executable stack with values ​​that are popped and pushed based on the instructions that are executed. Basic information about how managed code is transcoded and executed on the CPU is a separate issue in which traditional stack effects, registers, and pointer dereferencing are returned to the game. However, from the CLR point of view at the IL level, these things are (mostly) immaterial.

+1
source

There are four interrelated but different concepts: local variables in C #, local variables in CIL, a stack in CIL, and a native stack.

Note that how C # local locators map to CIL and how the CIL locals and the stack map in the internal memory are determined by the implementation, so you should not rely on this.

You know what C # locals is. They can be represented as local CIL users, but usually they do not go to the CIL stack (there may be some optimizations in the C # compiler). But there are other options: local can be fully optimized if it is not needed, or it can be compiled as a field in a class with an inexpressible name (lambda closure variables, variables in the yield or async method). In addition, even if some C # locales are compiled as local CILs, they do not need to display 1: 1, since one local CIL can be used for more C # locales if the compiler knows that it is safe.

There are local variables in CIL, and there is a stack. Local variables are completely separate from the stack, and there are different CIL instructions for working with each of them. Local variables are used to store values ​​that are needed for a longer time, and each local access can be obtained at any time. The CIL stack mainly contains values ​​that are used right now: parameters for instructions and their return values. Only the upper value can be accessed on the stack.

Both the local CIL locales and the CIL stack are actually pushed onto their own stack, but they are often only in registers if they fit. And, of course, the JIT compiler can do any other optimizations. As others have said, any value in the current stack of methods can be obtained at any time, and not just from above.

0
source

All Articles