2

问题

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

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

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

第二:好像把里面的所有reference( object)字段SB都移到了前面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; }
4

2 回答 2

5

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

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

似乎 SB 中的所有参考(对象)字段都被移动了

是的,这很正常。CLR 以未记录和无法发现的方式布置对象。它使用的确切规则没有记录,并且可能会发生变化。它也不会因不同的抖动而重复。在对象被编组、Marshal.StructureToPtr() 调用或由 pinvoke 编组器隐式调用之前,布局不会变得可预测。这是唯一一次确切的布局很重要。我在这个答案中写了关于这种行为的理由。

于 2013-03-13T18:28:06.710 回答
1

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

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

[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.explicit reference types] 的 Google 搜索揭示了一些有趣的讨论。请参阅在显式结构中相互覆盖多个 CLR 引用字段?, 例如。

于 2013-03-13T18:25:15.043 回答