C#有一个ref关键字.使用ref,您可以通过引用将int传递给方法.当您调用通过引用接受int的方法时,堆栈帧上会发生什么?
public void SampleMethod(ref int i) { }
Tamas Hegedu.. 18
在低级别,引用的局部int
变量将被放在堆栈上(大多数时候整数存储在寄存器中),并且指向堆栈的指针将被传递给调用的函数(指针本身最有可能被传入登记册).请考虑以下示例:
var i = 7; Console.WriteLine(i); inc(ref i); Console.WriteLine(i);
这将是JIT-et之类的东西(目标架构是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
这里发生了同样的事情,获得了字段或元素的地址,并将指针传递给函数:
var i = new[]{7}; Console.WriteLine(i[0]); inc(ref i[0]); Console.WriteLine(i[0]);
编译成(没有无聊的部分):
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]
请注意,在这种情况下不必固定数组,因为框架知道地址ecx
是指向数组内的项目,因此如果在inc函数之间lea
和之call
内发生堆压缩,它可以重新调整值的ecx
直接.
您可以通过打开Disassembly窗口(Debug/Windows/Disassembly)使用Visual Studio调试器自行调查JIT-ed程序集
在低级别,引用的局部int
变量将被放在堆栈上(大多数时候整数存储在寄存器中),并且指向堆栈的指针将被传递给调用的函数(指针本身最有可能被传入登记册).请考虑以下示例:
var i = 7; Console.WriteLine(i); inc(ref i); Console.WriteLine(i);
这将是JIT-et之类的东西(目标架构是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
这里发生了同样的事情,获得了字段或元素的地址,并将指针传递给函数:
var i = new[]{7}; Console.WriteLine(i[0]); inc(ref i[0]); Console.WriteLine(i[0]);
编译成(没有无聊的部分):
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]
请注意,在这种情况下不必固定数组,因为框架知道地址ecx
是指向数组内的项目,因此如果在inc函数之间lea
和之call
内发生堆压缩,它可以重新调整值的ecx
直接.
您可以通过打开Disassembly窗口(Debug/Windows/Disassembly)使用Visual Studio调试器自行调查JIT-ed程序集
局部变量或字段的地址.在IL中,ldloca.s
指令用于局部变量.
将特定索引处的局部变量的地址加载到评估堆栈中
该stind
指令用于将值存储回变量中
将类型(...)的值存储到地址的内存中
地址为32/64位,具体取决于目标架构.