代码之家  ›  专栏  ›  技术社区  ›  Foo

ref关键字如何工作(在内存方面)

  •  21
  • Foo  · 技术社区  · 9 年前

    C#有一个 ref 关键字。使用ref可以通过引用将int传递给方法。当您调用一个通过引用接受int的方法时,堆栈框架会发生什么?

    public void SampleMethod(ref int i) { }
    
    4 回复  |  直到 9 年前
        1
  •  19
  •   Tamas Hegedus    5 年前

    将局部变量作为引用传递

    在低级别,引用的本地 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]  
    

    请注意,在这种情况下,数组不必固定 ,因为 framework知道中的地址 ecx 指向数组内的项目, 所以如果堆压缩发生在 lea call 或者在inc函数内,它可以重新调整 欧洲期货交易所 直接地

    通过打开“反汇编”窗口,您可以使用Visual Studio调试器自己研究JIT编译的程序集( 调试/Windows/反汇编 )

        2
  •  5
  •   Jakub Lortz    9 年前

    局部变量或字段的地址。在IL中, ldloca.s 指令用于局部变量。

    将特定索引处的局部变量的地址加载到计算堆栈

    这个 stind 指令用于将值存储回变量中

    将类型(…)的值存储到地址为的内存中

    地址为32/64位,具体取决于目标体系结构。

        3
  •  4
  •   Hogan    9 年前

    下面是C#代码中的一个简单示例:

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

    这是生成的IL代码

    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     
    

    注意,在这个简单的案例中,ldloca实际上只有一个区别。s 00或ldloc.0。加载本地地址或加载地址(偏移量为00)

    这是最简单级别的区别(这是您在注释中要求的)——如果您加载变量的值或加载变量的地址。事情可能会很快变得复杂——如果你调用的函数不是本地的,如果你传递的变量不是本地的等等。但在基本层面上,这是不同的。

    我使用linqpad进行了快速diss组装——我推荐它。 http://www.linqpad.net/

        4
  •  0
  •   yazan_ati    9 年前

    它将通过引用传递局部变量,而不是为其发送新副本