How does the ref keyword work (in terms of memory)

后端 未结 4 2014
广开言路
广开言路 2021-02-12 13:48

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?



        
4条回答
  •  余生分开走
    2021-02-12 14:01

    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)

提交回复
热议问题