我试图了解为什么/如何在将refs返回给类成员的情况下返回。 换句话说,我想了解运行时的内部工作原理,它可以保证实例成员的ref-return从CLR的内存安全方面起作用。
ref-return documentation中提到了我所指的具体功能,具体说明:
返回值不能是返回它的方法中的局部变量;它的范围必须超出返回它的方法。它可以是类的实例或静态字段,也可以是传递给方法的参数。尝试返回局部变量会生成编译器错误CS8168,"无法返回本地' obj'通过引用,因为它不是本地的参考。"
这是一个代码片段,它干净地编译并运行并演示如何返回实例字段作为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);
}
}
}
我对此代码的问题是:GC中的基本机制是什么,以确保它在最后一次引用它之后跟踪SomeClass
实例应该被设置为null?
更确切地说......:
鉴于本地s
存储了指向SomeClass实例的_s
成员的直接指针(来自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
答案 0 :(得分:9)
// This will store a direct pointer to x.S
ref var s = ref x.S;
它存储一个指向堆变量的托管内部指针;指针存储在短期商店的位置。短期商店是GC的根。
// And now the GC will be free to re-use this memory
x = null;
天哪没有。 GC根目录中有一个活的托管内部指针。
在将SomeClass后备内存重新用于其他内容之后,.NET GC /运行时如何确保永远不会导致访问冲突或读取指针?
在内部托管指针不再是GC的根目录之前,不释放该内存。或者,换句话说:通过实现正确的垃圾收集器。
我无法弄清楚你在这里提出的问题。 GC可以防止错误,因为这是唯一的工作并且它已正确实现。
答案 1 :(得分:1)
在将SomeClass后备内存重新用于其他内容之后,.NET GC /运行时如何确保永远不会导致访问冲突或读取指针?
出于同样的原因,x
不会引用已经被GC清理过的对象。您知道这一点,因为如果对象可以通过托管代码访问,则不允许GC清理对象。在您清除对象之前,可以通过x
访问该对象,还可以s
。 s
是对该对象的引用,在确定是否允许收集该对象时,GC需要对其进行处理。