23

C# has a ref keyword. Using ref you can pass an int to a method by reference. What goes on the stack frame when you call a method that accepts an int by reference?

public void SampleMethod(ref int i) { }
7
  • Do you want to know the difference when it is by reference or everything that goes on the stack frame.
    – Hogan
    Commented Jan 2, 2016 at 18:21
  • Just the difference.
    – Foo
    Commented Jan 2, 2016 at 18:21
  • I'm wondering why you care about memory size here... since most modern machines will have no effect.
    – Hogan
    Commented Jan 2, 2016 at 18:24
  • I don't care about the memory size. I am just trying to understand how pass int by reference works.
    – Foo
    Commented Jan 2, 2016 at 18:44
  • Then why does your subject say "in terms of memory?"
    – Hogan
    Commented Jan 2, 2016 at 18:44

4 Answers 4

20

Passing a local variable as a reference

At low level, the referenced local int variable will be put on the stack (most of the time integers get stored in registers), and a pointer to the stack will be passed to the invoked function (the pointer itself is most likely to be passed in a register). Consider the following example:

var i = 7;
Console.WriteLine(i);
inc(ref i);
Console.WriteLine(i);

This will be JIT-et to something like this (target architecture is x86):

    17:             var i = 7;
    # allocate space on the stack for args and i
00482E3B  sub         esp,8  
    # initialize i to 0
00482E3E  xor         eax,eax  
00482E40  mov         dword ptr [ebp-8],eax  
    # args saved to stack (could be optimised out)  
00482E43  mov         dword ptr [ebp-4],ecx  
00482E46  cmp         dword ptr ds:[3ACAECh],0  
00482E4D  je          00482E54  
00482E4F  call        7399CB2D  
    # i = 7
00482E54  mov         dword ptr [ebp-8],7  
    18:             Console.WriteLine(i);
    # load the value of i into ecx, and call cw
00482E5B  mov         ecx,dword ptr [ebp-8]  
00482E5E  call        72E729DC  
    19:             inc(ref i);
    # load the address of i into ecx, and call inc
00482E63  lea         ecx,[ebp-8]  
00482E66  call        dword ptr ds:[4920860h]  
    20:             Console.WriteLine(i);
    # load the value of i into ecx, and call cw
00482E6C  mov         ecx,dword ptr [ebp-8]  
00482E6F  call        72E729DC  
    21:         }
00482E74  nop  
00482E75  mov         esp,ebp  
00482E77  pop         ebp  
00482E78  ret  

Passing an array item or an object member as a reference

Pretty much the same thing happens here, the address of the field or element is obtained, and the pointer is passed to the function:

var i = new[]{7};
Console.WriteLine(i[0]);
inc(ref i[0]);
Console.WriteLine(i[0]);

Compiles to (without the boring part):

    18:             Console.WriteLine(i[0]);
00C82E91  mov         eax,dword ptr [ebp-8]  
00C82E94  cmp         dword ptr [eax+4],0  
00C82E98  ja          00C82E9F  
00C82E9A  call        7399BDC2  
00C82E9F  mov         ecx,dword ptr [eax+8]  
00C82EA2  call        72E729DC  
    19:             inc(ref i[0]);
    # loading the reference of the array to eax
00C82EA7  mov         eax,dword ptr [ebp-8]  
    # array boundary check is inlined
00C82EAA  cmp         dword ptr [eax+4],0  
00C82EAE  ja          00C82EB5  
    # this would throw an OutOfBoundsException, but skipped by ja
00C82EB0  call        7399BDC2  
    # load the address of the element in ecx, and call inc
00C82EB5  lea         ecx,[eax+8]  
00C82EB8  call        dword ptr ds:[4F80860h]  

Note that the array doesn't have to be pinned in this case, because the framework knows about the address in ecx is pointing an item inside the array, so if a heap compression happens between lea and call or inside the inc function, it can readjust the value of ecx directly.

You can investigate the JIT-ed assembly yourself using Visual Studio debugger by opening the Disassembly window (Debug/Windows/Disassembly)

3
  • "the referenced int will be put on the stack" is not true, your example puts it on the stack with var i = 7; but ref would also work if it referenced a field of an object on the heap, or an element of an array.
    – Jon Hanna
    Commented Jan 2, 2016 at 19:50
  • @JonHanna Please read forward my answer. I edited the first sentence so it is more clear which case i am referring to Commented Jan 2, 2016 at 20:00
  • Yep. That nit picked, +1.
    – Jon Hanna
    Commented Jan 2, 2016 at 22:13
6

The address of the local variable or field. In the IL, ldloca.s instruction is used for a local variable.

Loads the address of the local variable at a specific index onto the evaluation stack

The stind instruction is used to store the value back in the variable

Store value of type (...) into memory at address

The address is 32/64 bit, depending on target architecture.

2
  • You mean a reference to a location on the stack is passed to the method?
    – Foo
    Commented Jan 2, 2016 at 18:42
  • 1
    @Foo A memory address. It doesn't have to be on the stack - a field can also be passed by ref. Commented Jan 2, 2016 at 18:53
5

Here is a simple example in C# code:

void Main()
{
    int i = 1;
    inc(ref i);
    Console.WriteLine(i);
}

public void inc(ref int i) { 
  i++;
}

Here is the generated IL code

IL_0000:  nop         
IL_0001:  ldc.i4.1    
IL_0002:  stloc.0     // i
IL_0003:  ldarg.0     
IL_0004:  ldloca.s    00 // i
IL_0006:  call        inc
IL_000B:  nop         
IL_000C:  ldloc.0     // i
IL_000D:  call        System.Console.WriteLine
IL_0012:  nop         
IL_0013:  ret         

inc:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  dup         
IL_0003:  ldind.i4    
IL_0004:  ldc.i4.1    
IL_0005:  add         
IL_0006:  stind.i4    
IL_0007:  ret     

Note with this simple case there is really only one difference ldloca.s 00 or ldloc.0. Load local or load address (of offset 00)

That is the difference at the simplest level (which is what you asked for in your comment) -- if you load the value of the variable or you load the address of the variable. Thing can get complicated quickly -- if the function you are calling is not local, if the variable you are passing is not local etc etc etc. But at a basic level this is the difference.

I used linqpad to do my quick diss-assembly -- I recommend it. http://www.linqpad.net/

1
  • I agree with @FrédéricHamidi the terminology boxing is not used in this context. But seen from a C# perspective, all of the details are just implementation... details. The runtime can do what it wants, as long as the same variable is used in the method as was specified at the call site. Commented Jan 2, 2016 at 18:38
0

It will pass the local variable by reference instead of sending a new copy for it

Not the answer you're looking for? Browse other questions tagged or ask your own question.