使用ref节省内存传递引用类型吗?

Mis*_*ith 7 .net c# ref reference-type pass-by-reference

在C#中,方法的参数可以是引用类型或值类型.传递引用类型时,将传递引用的副本.这样,如果在方法内部我们尝试将传递的引用重新分配给另一个对象实例,则在该方法之外,重新分配是不可见的.

为了使其工作,C#具有ref修饰符.使用ref传递引用类型实际上使用原始引用而不是副本.(如我错了请纠正我).

在这种情况下,由于我们没有创建引用的副本,我们是否保存了任何内存?如果广泛调用方法,这是否会提高应用程序的整体性能?

谢谢!

Meh*_*dad 16

要求

不,它没有.如果有的话,由于额外的查找,它会变慢.

没有理由按引用传递引用类型,除非你明确打算以后分配给它.


证明

由于有些人似乎认为编译器传递"变量本身 ",请查看此代码的反汇编:

using System;

static class Program
{
    static void Test(ref object o) { GC.KeepAlive(o); }

    static void Main(string[] args)
    {
        object temp = args;
        Test(ref temp);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是(在x86上,为简单起见):

// Main():
// Set up the stack
00000000  push        ebp                    // Save the base pointer
00000001  mov         ebp,esp                // Set up stack pointer
00000003  sub         esp,8                  // Reserve space for local variables
00000006  xor         eax,eax                // Zero out the EAX register

// Copy the object reference to the local variable `temp` (I /think/)
00000008  mov         dword ptr [ebp-4],eax  // Copy its content to memory (temp)
0000000b  mov         dword ptr [ebp-8],ecx  // Copy ECX (where'd it come from??)
0000000e  cmp         dword ptr ds:[00318D5Ch],0  // Compare this against zero
00000015  je          0000001C               // Jump if it was null (?)
00000017  call        6F910029               // (Calls some internal method, idk)

// THIS is where our code finally starts running
0000001c  mov         eax,dword ptr [ebp-8]  // Copy the reference to register
0000001f  mov         dword ptr [ebp-4],eax  // ** COPY it AGAIN to memory
00000022  lea         ecx,[ebp-4]            // ** Take the ADDRESS of the copy
00000025  call        dword ptr ds:[00319734h] // Call the method

// We're done with the call
0000002b  nop                                // Do nothing (breakpoint helper)
0000002c  mov         esp,ebp                // Restore stack
0000002e  pop         ebp                    // Epilogue
0000002f  ret                                // Return
Run Code Online (Sandbox Code Playgroud)

这是来自优化的代码编译.很明显,有一个变量的地址被传递,而不是"变量本身".

  • @Daniel:在两种情况下都有一个复制*的指针***.这只是一个直接指针或指向另一个指针的指针 - 当你使用`ref`时,它是后者,它涉及额外的内存查找(除非JIT通过证明它是不必要的来优化它). (4认同)
  • @Mehrdad:当你通过ref传递时,你没有传递一个指向指针的指针,但是你将唯一的指针传递给你的对象,而不是通过ref传递时创建一个新指针. (3认同)
  • 我认为不涉及额外的查找. (2认同)

Dan*_*rth 5

是的,有一个原因:如果你想重新分配价值.在这方面,值类型和引用类型没有区别.

请参阅以下示例:

class A
{
    public int B {get;set;}
}

void ReassignA(A a)
{
  Console.WriteLine(a.B);
  a = new A {B = 2};
  Console.WriteLine(a.B);
}

// ...
A a = new A { B = 1 };
ReassignA(a);
Console.WriteLine(a.B);
Run Code Online (Sandbox Code Playgroud)

这将输出:

1
2
1
Run Code Online (Sandbox Code Playgroud)

然而,性能与它无关.这将是真正的微观优化.


Mis*_*ith 5

Mehrdad的例子(两个版本)的分析视图

我会尝试深入研究Mehrdad的一个很好的证明,对于像我这样不太好读取汇编代码的人.当我们进行debbuging,单击Debug - > Windows - > Dissasembly时,可以在Visual Studio中捕获此代码.

使用REF的版本

源代码:

 namespace RefTest
 {
    class Program
    {
        static void Test(ref object o) { GC.KeepAlive(o); }

        static void Main(string[] args)
        {
            object temp = args;
            Test(ref temp);
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

汇编语言(x86)(仅显示不同的部分):

             object temp = args;
 00000030  mov         eax,dword ptr [ebp-3Ch] 
 00000033  mov         dword ptr [ebp-40h],eax 
             Test(ref temp);
 00000036  lea         ecx,[ebp-40h] //loads temp address's address on ecx? 
 00000039  call        FD30B000      
 0000003e  nop              
         }  
Run Code Online (Sandbox Code Playgroud)

版本没有REF

源代码:

 namespace RefTest
 {
    class Program
    {
        static void Test(object o) { GC.KeepAlive(o); }

        static void Main(string[] args)
        {
            object temp = args;
            Test(temp);
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

汇编语言(x86)(仅显示不同的部分):

             object temp = args;
 00000035  mov         eax,dword ptr [ebp-3Ch] 
 00000038  mov         dword ptr [ebp-40h],eax 
             Test(temp);
 0000003b  mov         ecx,dword ptr [ebp-40h] //move temp address to ecx?
 0000003e  call        FD30B000 
 00000043  nop              
         }
Run Code Online (Sandbox Code Playgroud)

除了注释行之外,两个版本的代码都是相同的:使用ref,对函数的调用前面是LEA指令,没有ref我们有一个更简单的MOV指令.在执行该行之后,LEA已经为ecx寄存器加载了一个指向该对象的指针,而MOV已经为ecx加载了一个指向该对象的指针.这意味着在第一种情况下FD30B000子程序(指向我们的测试功能)必须额外访问内存才能到达对象.如果我们检查这个函数的每个生成版本的汇编代码,我们可以看到在某些时候(实际上是两个版本之间唯一不同的行),进行了额外的访问:

static void Test(ref object o) { GC.KeepAlive(o); }
...
00000025  mov         eax,dword ptr [ebp-3Ch] 
00000028  mov         ecx,dword ptr [eax]
...
Run Code Online (Sandbox Code Playgroud)

虽然没有ref的函数可以直接到对象:

static void Test(object o) { GC.KeepAlive(o); }
...
00000025  mov         ecx,dword ptr [ebp-3Ch]
...
Run Code Online (Sandbox Code Playgroud)

希望它有所帮助.