C# 对于本身为结构的字段,LayoutKind.explicit的.NET行为 问题:
我尝试使用C# 对于本身为结构的字段,LayoutKind.explicit的.NET行为 问题:,c#,.net,struct,offset,layoutkind.explicit,C#,.net,Struct,Offset,Layoutkind.explicit,我尝试使用[StructLayout(LayoutKind.Explicit)]构建一个结构(SA),其中有一个字段是另一个结构(SB) 首先:我很惊讶我被允许在没有[StructLayout(LayoutKind.Explicit)]的情况下声明另一个结构,而在SA中,所有字段都必须有[FieldOffset(0)],否则编译器会大喊大叫。这没有多大意义 这是编译器警告/错误中的漏洞吗 Second:似乎SB中的所有引用(object)字段都移动到了SB的前面 这种行为在哪里被描述过 它
[StructLayout(LayoutKind.Explicit)]
构建一个结构(SA
),其中有一个字段是另一个结构(SB
)
首先:我很惊讶我被允许在没有[StructLayout(LayoutKind.Explicit)]
的情况下声明另一个结构,而在SA
中,所有字段都必须有[FieldOffset(0)]
,否则编译器会大喊大叫。这没有多大意义
- 这是编译器警告/错误中的漏洞吗
Second:似乎SB
中的所有引用(object
)字段都移动到了SB
的前面
- 这种行为在哪里被描述过
- 它是否取决于实现
- 它是否定义在依赖于实现的任何地方<代码>:)
注意:我不打算在生产代码中使用此选项。我问这个问题主要是出于好奇
实验
第一个问题的答案是否定的,编译器的错误报告中没有漏洞或bug。如果您开始执行显式布局,编译器将假定您知道自己在做什么(在限制范围内——请参见下文)。你让它把一个结构叠加在另一个上面。编译器不(也不应该)关心您覆盖的结构是否也没有显式布局 如果编译器真的在意,那么您将无法覆盖任何未显式布局的类型,这意味着您无法在一般情况下进行联合。例如,尝试覆盖<代码>日期时间<代码>和<代码>长< /代码>:
[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]上的搜索揭示了一些有趣的讨论。例如,见
这是编译器警告/错误中的漏洞吗
不,没问题。字段允许重叠,这就是为什么LayoutKind.Explicit首先存在的原因。它允许在非托管代码中声明联合的等价物,而C#不支持这种声明。不能突然停止在结构声明中使用[FieldOffset],运行时会坚持在结构的所有成员上使用它。这在技术上不是必需的,只是一个避免错误假设的简单要求
似乎SB中的所有引用(对象)字段都已移动
是的,这很正常。CLR以未记录和不可发现的方式布置对象。它使用的确切规则没有文档记录,可能会发生更改。对于不同的抖动,它也不会重复。直到对象被封送、Marshal.StructureToPtr()调用或被pinvoke封送器隐式封送,布局才变得可预测。这是唯一一次确切的布局问题。我在中写到了这种行为的基本原理。MyUnion结构没有TypeLoadException。只有当您将值类型值与引用类型对象重叠时,它才会崩溃。就像那次行动一样。试试看,似乎。。。危险的或许不是。嗯。我想覆盖两个引用类型是安全的。如果在将
o1
设置为非SomeRefType
的对象后尝试取消对o2
的引用,则会发生运行时异常。这对我来说似乎很危险:可以使用不同的类型覆盖两个数组。这允许您访问较小数组区域以外的内存。@Ark kun:运行时将防止引用类型与值类型重叠。如果覆盖两种值类型,则分配的内存量将是两种值类型中较大的一种。因此,您提到的场景不是问题。@JimMischel覆盖两个数组并不是“用值类型覆盖引用类型”。数组是.Net中的引用。重叠数组允许绕过CLR类型检查和数组边界保护以及损坏的内存。如果我没有弄错的话,当我们将原始不安全指针转换为指向该结构的指针时,该结构也具有“类似封送的布局”,不是吗?比如说,在不安全的{SA*SA=(SA*)0x1234;/*在这里使用SA*/}
中,SA
将具有与封送时相同的布局,不是吗?不,这不是封送。编译器不允许您这样做,结构是不可blittable的。确实,它只适用于包含int的结构,但不适用于添加对象字段的结构。然而,对于只包含tint
s之类的结构,这个问题仍然存在,就像我使用的第一个结构一样。
struct MyUnion
{
[FieldOffset(0)]
public object o1;
[FieldOffset(0)]
public SomeRefType o2;
}