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

时间:2010-04-24 07:25:15

标签: c# .net struct 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的一个黑暗角落,而且这种技术在极少数特定情况下才是可行的选择。

5 个答案:

答案 0 :(得分:3)

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

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

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

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

答案 1 :(得分:2)

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

这会更安全:

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,它会变得更加棘手。

答案 2 :(得分:2)

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

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

看看this page详细介绍了C#结构转换为IL的一些方法,您会注意到IL / CLR本身内置了许多内存布局。

答案 3 :(得分:1)

由于垃圾收集器是无类型的,只能区分对象引用和普通位,重叠的引用不会混淆它。但是,虽然一个对象引用可以完全与另一个对象引用重叠,但这是无法验证的,也就是说不安全(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之后的this article,有一个句柄表。将其作为函数指针处理会导致System.AccessViolationException。

答案 4 :(得分:-1)

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