百科问答小站 logo
百科问答小站 font logo



.NET CLR怎么保证执行正确的unsafe代码不挂掉? 第1页

  

user avatar   rednaxelafx 网友的相关建议: 
      

简短回答:

第一个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)分为两类:

  • verifiable code:普通的C#代码属于这类。代码的类型安全可以通过静态校验来保证。不可以使用裸指针。
  • unverifiable code:也就是unsafe code。CLR无法通过静态校验来保证类型安全,程序员要自己保证写的代码的语义是正确的。在unverifiable code中可以使用裸指针(非托管指针),指针既可以指向C-style的非托管内存,也可以指向被pin住的托管堆中的对象。还可以调用非托管的函数指针(calli)。

注意: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。


user avatar   Ivony 网友的相关建议: 
      

一、

1、实现上一般不是用地址的地址,一般用直接地址。

2、但是CLI应该没有要求用哪种形式,换言之用地址的地址实现也是可以的,但是考虑到效率问题一般都会用直接地址吧,毕竟只有GC的时候才需要修改。

3、GC回收的时候会合并内存,所以托管对象的地址会改变,与此同时引用会跟着改。

二、fixed不就是干这个的么?


R大说的是对的,一开始我没看到地址的地址,想当然的以为你问的是GC会不会移对象和更新地址,答案是是。




  

相关话题

  如何自学 .NET Core,怎么学习才好? 
  为什么那么多公司不用 .NET,而选择 PHP、JSP,是 .NET 有什么缺点吗? 
  为何ASP.NET MVC在github上面只能找到6.0的release? 
  C#的async和await底层是怎么做到的? 
  C#是如何做到闪电编译时? 
  为什么微软的编程语言C# F#的编译器要那么多黑科技? 
  如何看待即将发布的 C# 8.0? 
  用 C# 写个方法解析简单的 JSON 字符串有哪些思路? 
  WPF中如何在Parallel.For中利用Dispatcher.Invoke实时更新进度条? 
  为何我感觉 Visual Studio 很难用? 

前一个讨论
苹果为何不对其他厂家授权其操作系统?
下一个讨论
《这个历史挺靠谱》一书所述内容有多少错误的地方?





© 2024-11-25 - tinynew.org. All Rights Reserved.
© 2024-11-25 - tinynew.org. 保留所有权利