ref 关键字如何工作(就内存而言) [英] How does the ref keyword work (in terms of memory)
问题描述
C# 有一个 ref 关键字.使用 ref 您可以通过引用将 int 传递给方法.当您调用通过引用接受 int 的方法时,堆栈帧上会发生什么?
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) { }
推荐答案
传递局部变量作为引用
在底层,引用的本地int
变量将被放入堆栈(大多数情况下整数存储在寄存器中),并且指向堆栈的指针将被传递给被调用的函数(指针本身最有可能在寄存器中传递).考虑以下示例:
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);
这将是类似这样的 JIT-et(目标架构是 x86):
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]
请注意,在这种情况下不必固定数组,因为框架知道 ecx
中的地址指向数组内的一个项目,所以如果堆压缩发生在lea
和call
之间或inc函数内部,它可以直接重新调整ecx
的值.
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.
您可以通过打开反汇编窗口 (Debug/Windows/Disassembly) 使用 Visual Studio 调试器自己调查 JIT 化的程序集
You can investigate the JIT-ed assembly yourself using Visual Studio debugger by opening the Disassembly window (Debug/Windows/Disassembly)
这篇关于ref 关键字如何工作(就内存而言)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!