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?
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/
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
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)
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.
It will pass the local variable by reference instead of sending a new copy for it