在显式结构中将几个CLR引用字段相互叠加?

编辑:我很清楚这对于值类型非常有效,我的具体问题是将其用于引用类型。

Edit2:我也知道你不能在结构中覆盖引用类型和值类型,这只是为了将几个引用类型字段相互重叠。

我一直在修补.NET / C#中的结构,我发现你可以这样做:

using System; using System.Runtime.InteropServices; namespace ConsoleApplication1 { class Foo { } class Bar { } [StructLayout(LayoutKind.Explicit)] struct Overlaid { [FieldOffset(0)] public object AsObject; [FieldOffset(0)] public Foo AsFoo; [FieldOffset(0)] public Bar AsBar; } class Program { static void Main(string[] args) { var overlaid = new Overlaid(); overlaid.AsObject = new Bar(); Console.WriteLine(overlaid.AsBar); overlaid.AsObject = new Foo(); Console.WriteLine(overlaid.AsFoo); Console.ReadLine(); } } } 

基本上避免在运行时通过使用具有显式字段布局的结构进行动态转换,然后以正确的类型访问内部对象。

现在我的问题是:这是否会以某种方式导致内存泄漏,或CLR内部的任何其他未定义的行为? 或者这是一个完全支持的约定,可以使用而没有任何问题?

我知道这是CLR的黑暗角落之一,而且这种技术在极少数特定情况下才是可行的选择。

我无法看到显式布局版本如何在没有运行时注入额外检查的情况下可以validation,因为它允许您查看非声明类型的非空引用。

这会更安全:

 struct Overlaid { // could also be a class for reference-type semantics private object asObject; public object AsObject {get {return asObject;} set {asObject = value;} } public Foo AsFoo { get {return asObject as Foo;} set {asObject = value;} } public Bar AsBar { get {return asObject as Bar;} set {asObject = value;} } } 

没有撕裂引用的风险等,仍然只有一个字段。 它不涉及任何有风险的代码等。特别是,它不会冒任何愚蠢的风险:

  [FieldOffset(0)] public object AsObject; [FieldOffset(0)] public Foo AsFoo; [FieldOffset(1)] public Bar AsBar; // kaboom!!!! 

另一个问题是,除非可以保证CPU模式,否则您只能以这种方式支持单个字段; offset 0很简单,但如果你需要多个字段并且需要支持x86和x64,它会变得更加棘手。

好吧,你发现了一个循环孔,CLR允许它,因为所有重叠的字段都是对象。 任何允许你直接搞乱对象引用的东西都会被TypeLoadException拒绝:

  [StructLayout(LayoutKind.Explicit)] struct Overlaid { [FieldOffset(0)] public object AsObject; [FieldOffset(0)] public IntPtr AsPointer; } 

但是你可以通过给出类字段来利用它。 只要您只是读取字段值,就不会发生任何错误,例如,您可以获得跟踪句柄的值。

但是,编写这些字段会导致ExecutionEngineException。 但是我认为如果你能正确猜出跟踪句柄的值,它就是一个漏洞。 但实际使用情况接近于零。

如果以不安全的方式对齐类型,即使使用/unsafe编译,运行时也会在加载时抛出TypeLoadException 。 所以我觉得你很安全。

我猜 – 因为你可以使用StructLayout并编译没有/unsafe标志的代码 – 这是CLR的一个特性。 您需要StructLayout属性,因为C#没有以这种方式声明类型的直接方法。

看一下这个详细介绍C#结构转换为IL的方式的页面 ,您会注意到IL / CLR本身内置了许多内存布局。

由于垃圾收集器是无类型的,只能区分对象引用和普通位,重叠引用不会混淆它。 但是,虽然一个对象引用可以完全与另一个对象引用重叠,但这是无法validation的,也就是说不安全(ECMA-335标准,第180页,II.10.7控制实例布局)。 很容易构建一个利用这种无法实现的程序来骇人听闻地崩溃:

 using System.Runtime.InteropServices; class Bar { public virtual void func() { } } [StructLayout(LayoutKind.Explicit)] struct Overlaid { [FieldOffset(0)] public object foo; [FieldOffset(0)] public Bar bar; } class Program { static void Main(string[] args) { var overlaid = new Overlaid(); overlaid.foo = new object(); overlaid.bar.func(); } } 

这里func调用从对象类的虚拟表的最后一个元素开始加载一个函数指针。 根据vtbl之后的这篇文章 ,有一个句柄表。 将其作为函数指针处理会导致System.AccessViolationException。

我不知道它有任何问题。 此外,我怀疑如果以非显而易见的方式存在危险,微软会允许这种用法。