简短回答:
第一个reference是不是类似于Handle, 类似于一个地址的地址, 这样.NET CLR在GC的时候, 可以挪动那个对象, 同时修改reference指向地址里面保存的地址. 具体实现的时候是不是这样?
CLI VES规范里并没有要求具体实现采用什么方式实现managed pointer。具体到微软的CLR / CoreCLR的实现的话,普通的managed pointer是个直接指针,而不是“指针的指针”——后者叫做handle,这种方式实现的堆叫做handle-based heap。
关于CLR的对象模型的实现,以及与其它一些JVM实现之类的对比,请跳传送门:
为什么bs虚函数表的地址(int*)(&bs)与虚函数地址(int*)*(int*)(&bs) 不是同一个? - RednaxelaFX 的回答这边的例子可以显示出direct-pointer-based heap与handle-based heap的差异。
显然,对于要支持对象移动的GC heap,handle-based heap更容易实现,但这样每次访问对象内容都要做双层解引用,性能(访问效率)比使用直接指针的方案要差。
CLR的managed pointer使用直接指针实现,而CLR的GC又可以移动对象,这就要求CLR能够准确跟踪所有managed pointer所在的位置,无论它是“栈上”的局部变量、堆里的字段还是从VM内部出发的引用,以保证CLR能够:
于是CLR的GC默认的工作模式是“准确式GC”(precise GC,或者叫type-exact GC)。程序的元数据会准确指出栈上的变量和堆里对象的字段哪些是托管指针。相关讨论请跳传送门:
找出栈上的指针/引用CLR也有一种备用的“保守式GC”(conservative GC)模式,不要求程序对栈上的变量是否为托管指针提供准确的描述。这主要是在开发过程的早期使用的,而在实际发布的产品中并没有启用这种模式。
近期使用了这种模式的项目之一是LLILC,微软尝试给CoreCLR做的基于LLVM的新编译器。它为了早期开发方便而选择先抄个捷径,但这个决定似乎对后期开发带来了负面影响:
[八卦] LLILC项目貌似挂了… - 编程语言与高级语言虚拟机杂谈(仮) - 知乎专栏第二个是, 一个实现上没有错误的unsafe代码, .NET CLR是不是需要保证用的对象不被挪走?
CLR可以执行的托管代码(managed code)分为两类:
注意:unverifiable code仍然是managed code。MSIL有专门的unsafe子集来表达unsafe语义。
上面描述的重点内容之一,是指向托管堆对象的unsafe code中的指针,只能指向被pin住的对象。这个“object pinning”语义在C#里是通过fixed关键字来表达的。
为了高效地支持unsafe code(以及诸如System.Runtime.InteropServices.GCHandle的功能),CLR的GC必须要直接支持object pinning——即便托管堆里有对象正在被pin住,GC也要可以正常执行。
反例是例如HotSpot JVM,它的GC们都不直接支持object pinning,因而在执行JNI的critical系API(要求暂时不移动某些对象)时不得不暂时禁止GC执行。这就可以很悲剧…
如果不使用GCHandle,CLR只保证对象在unsafe code里是被pin住的,所以如果故意在unsafe code里把指向被pin住的对象的指针传递给unmanaged code保存起来,然后managed code一侧离开unsafe code之后,unmanaged code还试图去使用之前存下来的指针,那语义就是没有保证的——对象可能已经被挪走了。
<-
@vczh的回答里提到的例子就是这种情况。要保证安全的话,最好是在传出指针给unmanaged code之前就在safe一侧创建合适的GCHandle把目标对象一直pin住,直到unmanaged code不再需要那个指针才撤销safe这边的GCHandle。
一、
1、实现上一般不是用地址的地址,一般用直接地址。
2、但是CLI应该没有要求用哪种形式,换言之用地址的地址实现也是可以的,但是考虑到效率问题一般都会用直接地址吧,毕竟只有GC的时候才需要修改。
3、GC回收的时候会合并内存,所以托管对象的地址会改变,与此同时引用会跟着改。
二、fixed不就是干这个的么?
R大说的是对的,一开始我没看到地址的地址,想当然的以为你问的是GC会不会移对象和更新地址,答案是是。