如何/为什么ref返回实例成员

dam*_*boy 7 c# jit garbage-collection return ref

我试图理解为什么/如何在将refs返回给类成员的情况下返回.换句话说,我想了解运行时的内部工作原理,它可以保证实例成员的ref-return从CLR的内存安全方面起作用.

我引用的具体功能在ref-return文档中提到,该 文档具体说明:

返回值不能是返回它的方法中的局部变量; 它的范围必须超出返回它的方法.它可以是类的实例或静态字段,也可以是传递给方法的参数.尝试返回局部变量会生成编译器错误CS8168,"无法通过引用返回本地'obj',因为它不是ref本地."

这是一个完整编译和运行的代码片段,演示如何返回实例字段作为ref return:

using System;
using System.Diagnostics;

namespace refreturn
{
    public struct SomeStruct {
        public int X1;
    }

    public class SomeClass {
        SomeStruct _s;
        public ref SomeStruct S => ref _s;
    }

    class Program
    {
        static void Main(string[] args)
        {
            var x = new SomeClass();                     
            // This will store a direct pointer to x.S
            ref var s = ref x.S;               
            // And now the GC will be free to re-use this memory
            x = null;          
            // Why should s.X1 be considered safe?
            Console.WriteLine(s.X1 + 0x666);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我对这段代码的问题是:GC中的底层机制是什么,以确保它SomeClass在最后一次引用之后一直跟踪实例,据说它已被设置为null?更确切地说...:

鉴于本地s存储指向_sSomeClass实例的成员的直接指针(从windbg反汇编),其最后一个"显式"引用在下一行(x = null)中用null覆盖,GC如何跟踪活根到SomeClass会阻止这个程序崩溃的实例......?

Windbg反汇编:

007ff9`e01504dc e8affbffff      call    00007ff9`e0150090 (refreturn.SomeClass.get_S(), mdToken: 0000000006000001)
                                  //rbp-30h stores the pointer to the struct
00007ff9`e01504e1 488945d0        mov     qword ptr [rbp-30h],rax
                                  // Now it's copied to rcx
00007ff9`e01504e5 488b4dd0        mov     rcx,qword ptr [rbp-30h]
                                  // And now copied to rbp-20h
00007ff9`e01504e9 48894de0        mov     qword ptr [rbp-20h],rcx
00007ff9`e01504ed 33c9            xor     ecx,ecx
                                  // The last reference is overwritten with null
00007ff9`e01504ef 48894de8        mov     qword ptr [rbp-18h],rcx
                                  // rbp-20h is copied to rcx again
00007ff9`e01504f3 488b4de0        mov     rcx,qword ptr [rbp-20h]
                                  // Isn't this a possible boom?!?!?
00007ff9`e01504f7 8b09            mov     ecx,dword ptr [rcx]
00007ff9`e01504f9 81c19a020000    add     ecx,29Ah
00007ff9`e01504ff e85c634c5d      call    mscorlib_ni+0xd56860 (00007ffa`3d616860) (System.Console.WriteLine(Int32), mdToken: 0000000006000b5b)
00007ff9`e0150504 90              nop
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 9

 // This will store a direct pointer to x.S
 ref var s = ref x.S; 
Run Code Online (Sandbox Code Playgroud)

它存储一个托管堆内变量的内部指针; 指针存储在短期商店的位置.短期商店是GC的根.

// And now the GC will be free to re-use this memory
x = null;   
Run Code Online (Sandbox Code Playgroud)

天哪没有. GC根目录中有一个活的托管内部指针.

.NET GC /运行时如何确保在SomeClass后备内存被重新用于其他内容之后,这永远不会导致访问冲突或读取野指针?

在内部托管指针不再是GC的根目录之前,不释放该内存.或者,换句话说:通过实现正确的垃圾收集器.

我无法弄清楚你在这里问的是什么问题.GC可以防止错误,因为这是它唯一的工作,并且它已正确实现.

  • @damageboy:**是**.你会注意到在C#7中你可以在一个调用中传递一个`ref`,你可以在本地放一个`ref`,你可以从一个调用中返回一个`ref`,并且*就是全部*.你不能把`ref`放到字段或数组元素中.你不能在locals和temporaries作为闭包类的字段(如迭代器块或异步方法)的环境中使用`ref`.你不能在这些上下文中使用一个结构的`this` - 这是秘密的`ref` - (或者,在某些情况下,值被复制). (2认同)