LayoutKind.explicit对于本身是结构的字段的.NET行为

本文关键字:字段 NET 行为 结构 explicit 于本身 LayoutKind | 更新日期: 2023-09-27 18:03:57

问题

我尝试使用[StructLayout(LayoutKind.Explicit)]构建一个结构(SA(,其中有一个字段是另一个struct(SB(。

第一个:我很惊讶我被允许声明没有[StructLayout(LayoutKind.Explicit)]的其他结构,而在SA中,所有字段都必须[FieldOffset(0)],否则编译器会大喊。这没有多大意义。

  • 这是编译器警告/错误中的漏洞吗

第二:似乎SB中的所有引用(object(字段都移到了SB的前面。

  • 这种行为在任何地方都有描述吗
  • 它是否取决于实施
  • 它是否在任何地方被定义为依赖于实现?:)

注意:我不打算在生产代码中使用它。我问这个问题主要是出于好奇。

实验

// No object fields in SB
// Gives the following layout (deduced from experimentation with the C# debugger):
// | f0 | f4 and i | f8 and j | f12 and k | f16 |
[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] int f4;
    [FieldOffset(8)] int f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; int k; }
// One object field in SB
// Gives the following layout:
// | f0 | f4 and o1 | f8 and i | f12 and j | f16 and k |
// If I add an `object` field after `j` in `SB`, i *have* to convert
// `f4` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.
[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] object f4;
    [FieldOffset(8)] int f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; int k; }
// Two `object` fields in `SB`
// Gives the following layout:
// | f0 | f4 and o1 | f8 and o2 | f12 and i | f16 and j | k |
// If I add another `object` field after the first one in `SB`, i *have* to convert
// `f8` to `object`, otherwise I get a `TypeLoadException`.
// No other field will do.
[StructLayout(LayoutKind.Explicit)]
struct SA {
    [FieldOffset(0)] int f0;
    [FieldOffset(4)] SB sb;
    [FieldOffset(4)] object f4;
    [FieldOffset(8)] object f8;
    [FieldOffset(12)] int f12;
    [FieldOffset(16)] int f16;
}
struct SB { int i; int j; object o1; object o2; int k; }

LayoutKind.explicit对于本身是结构的字段的.NET行为

这是编译器警告/错误中的漏洞吗?

不,没有错。字段可以重叠,这就是LayoutKind.Explicit首先存在的原因。它允许在非托管代码中声明等价的并集,而C#不支持这种方法。您不能突然停止在结构声明中使用[FieldOffset],运行时会坚持要求您在结构的所有成员上使用它。技术上没有必要,但这是一个简单的要求,可以避免错误的假设。

SB中的所有引用(对象(字段似乎都被移动了

是的,这很正常。CLR以一种未记录和不可覆盖的方式布置对象。它使用的确切规则没有记录在案,可能会发生变化。对于不同的抖动,它也不会重复。只有通过Marshal.StructeToPtr((调用或通过pinvoke封送器隐式封送对象,布局才能变得可预测。这是唯一一次确切的布局很重要。我在这个答案中写到了这种行为的基本原理。

第一个问题的答案是否定的,编译器的错误报告中没有漏洞或bug。如果您开始进行显式布局,编译器将假设您知道自己在做什么(在限制范围内——见下文(。你让它把一个结构叠加在另一个结构上。编译器不(也不应该(关心你覆盖的结构没有明确布局。

如果编译器关心,那么您将无法覆盖任何未明确布局的类型,这意味着在一般情况下您无法进行并集。例如,考虑尝试覆盖DateTimelong:

[StructLayout(LayoutKind.Explicit)]
struct MyUnion
{
    [FieldOffset(0)]
    public bool IsDate;
    [FieldOffset(1)]
    public DateTime dt;
    [FieldOffset(1)]
    public long counter;
}

除非明确地布置了DateTime,否则它不会编译。可能不是你想要的。

就将引用类型放在显式布局的结构中而言,您的结果将是。。。可能不是你所期望的。例如,考虑这个简单的位:

struct MyUnion
{
    [FieldOffset(0)]
    public object o1;
    [FieldOffset(0)]
    public SomeRefType o2;
}

这在很大程度上违反了类型安全。如果它进行编译(很可能(,当你尝试使用它时,它将死于TypeLoadException

编译器将尽可能防止您违反类型安全。我不知道编译器是否知道如何处理这些属性和布局结构,或者它是否只是通过生成的MSIL将布局信息传递给运行时。考虑到第二个例子,可能是后者,编译器允许特定的布局,但运行时被TypeLoadException轰炸。

谷歌搜索[structlayout.explicity引用类型]可以发现一些有趣的讨论。请参阅在显式结构中重叠多个CLR引用字段?,例如