代码之家  ›  专栏  ›  技术社区  ›  thr

在显式结构中重叠几个CLR引用字段?

  •  10
  • thr  · 技术社区  · 14 年前

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

    编辑2: 我还知道,不能在结构中覆盖引用类型和值类型,这仅适用于相互覆盖多个引用类型字段的情况。

    我一直在修改.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 回复  |  直到 11 年前
        1
  •  2
  •   Marc Gravell    14 年前

    如果没有运行时注入额外的检查,我看不到显式布局版本是如何验证的。 无论如何 ,因为它允许您看到对非声明类型的对象的非空引用。

    这样更安全:

    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模式,否则只能支持单个字段;偏移量0很容易,但如果需要多个字段并需要支持x86和x64,则会变得更复杂。

        2
  •  3
  •   Hans Passant    14 年前

    你发现了一个循环孔,clr允许这样做,因为所有重叠的字段都是对象。任何会让您直接处理对象引用的内容都会被类型加载异常拒绝:

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

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

    但是,编写这些字段会导致执行异常。不过,如果您能正确猜测跟踪句柄的值,我认为这是一个漏洞。但实际使用已经足够接近零了。

        3
  •  2
  •   chakrit Dutchie432    14 年前

    如果以不安全的方式对齐类型,运行时将引发 TypeLoadException 即使在编译时 /unsafe . 所以我认为你是安全的。

    我猜——因为你可以用 StructLayout 编译你的代码 /不安全 标志——这是clr的一个特性。您只需要structlayout属性,因为c没有直接的方法来这样声明类型。

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

        4
  •  1
  •   Bruno Martinez    11 年前

    由于垃圾收集器是非类型化的,并且只区分对象引用和普通位,重叠的引用不会混淆它。但是,虽然一个对象引用可以完全重叠另一个对象引用,但这是不可验证的,即不安全的(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调用从对象类的虚拟表的最后一个元素的前面加载一个函数指针。根据 this article 在vtbl后面有一个句柄表。将其视为函数指针会导致System.AccessViolationException。

        5
  •  -1
  •   Matt Howells    14 年前

    我不知道有什么问题。此外,我怀疑微软会允许这种使用,如果它是危险的,在一个不明显的方式。