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

本文关键字:节省 内存 是否 引用类型 ref 使用 | 更新日期: 2023-09-27 18:06:41

在c#中,方法的形参可以是引用类型,也可以是值类型。当传递引用类型时,将传递引用的副本。这样,如果在方法内部尝试将传递的引用重新赋值给另一个对象实例,则在方法外部,重赋值是不可见的。

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

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

谢谢!

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

索赔

不,没有。如果有什么区别的话,那就是由于额外的查找,它变慢了。

没有理由通过引用传递引用类型,除非你特别打算稍后给它赋值。

由于有些人似乎认为编译器传递了变量本身&;,看一下这段代码的反汇编:

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);
    }
}

(为了简单起见,在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

这是来自优化的代码编译。显然,传递的是变量的地址,而不是"变量本身"。

DISSASEMBLER VIEW OF the example OF Mehrdad(两个版本)

对于像我这样不擅长阅读汇编代码的人,我将尝试更深入地挖掘Mehrdad的出色证明。当我们在调试时,点击Debug -> Windows ->反汇编,可以在Visual Studio中捕获这些代码。

VERSION USING 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);
        }
    }
 }
汇编语言(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              
         }  

VERSION WITHOUT REF

源代码:

 namespace RefTest
 {
    class Program
    {
        static void Test(object o) { GC.KeepAlive(o); }
        static void Main(string[] args)
        {
            object temp = args;
            Test(temp);
        }
    }
 }
汇编语言(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              
         }

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

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

而不带ref的函数可以直接进入对象:

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

希望有帮助。

是的,有一个原因:如果您想重新赋值。在这方面,值类型和引用类型没有区别。

请看下面的例子:

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);

这将输出:

1
2
1
然而,性能与它无关。这才是真正的微优化

按值传递引用类型不会复制对象。它只创建对现有对象的新引用。所以你不应该通过引用传递它,除非你真的需要。