我们必须与本机代码交互很多,在这种情况下,使用不需要编组的不安全结构会快得多。但是,当结构包含非原始类型的固定大小缓冲区时,我们无法执行此操作。 为什么C#编译器要求固定大小的缓冲区只是原始类型?为什么固定大小的缓冲区不能由结构组成,例如:
[StructLayout(LayoutKind.Sequential)]
struct SomeType
{
int Number1;
int Number2;
}
答案 0 :(得分:17)
C#中的固定大小缓冲区使用名为“opaque classes”的CLI功能实现。 Ecma-335的第I.12.1.6.3节描述了它们:
某些语言提供多字节数据结构,其内容由直接操作 地址算术和间接操作。为了支持此功能,CLI允许值类型 要使用指定的大小创建,但没有关于其数据成员的信息。实例 这些“不透明类”的处理方式与任何其他类的实例完全相同,但是 ldfld,stfld,ldflda,ldsfld和stsfld指令不得用于访问其内容。
“没有关于他们的数据成员的信息”和“不得使用”ldfld / stfld“。第二条规则将kibosh放在结构上,你需要ldfld和stfld来访问它们的成员。 C#编译器无法提供替代方案,struct的布局是运行时实现细节。也是您无法在结构上使用 sizeof 运算符的原因。十进制和可空的<>因为它们也是结构而出局了。 IntPtr是因为它的大小取决于进程的位数,因此C#编译器很难为用于访问缓冲区的ldind / stind操作码生成地址。引用类型引用已经过时,因为GC需要能够找回它们,而不能通过第一条规则找到它们。枚举类型具有可变大小,取决于其基本类型;听起来像是一个可解决的问题,并不完全确定他们为什么会跳过它。
其中只留下C#语言规范中提到的那些:sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double或bool。只是具有明确定义的简单类型。
答案 1 :(得分:5)
来自MSDN:
在C#中,您可以使用fixed语句在数据结构中创建具有固定大小数组的缓冲区。当您使用现有代码时,这很有用,例如用其他语言编写的代码,预先存在的DLL或COM项目。固定数组可以采用常规struct成员允许的任何属性或修饰符。唯一的限制是数组类型必须是 bool,byte,char,short,int,long,sbyte,ushort,uint,ulong,float或double 。
我只想引用Hans Passant先生关于为什么固定缓冲区必须为unsafe
。您可能会看到Why is a fixed size buffers (arrays) must be unsafe?以获取更多信息。
因为“固定缓冲区”不是真正的数组。它是一种自定义值类型,关于唯一的方式 用我知道的C#语言生成一个。没有办法 CLR验证数组的索引是否以安全的方式完成。 代码也不可验证。最具图形的演示 这样:
using System;
class Program {
static unsafe void Main(string[] args) {
var buf = new Buffer72();
Console.WriteLine(buf.bs[8]);
Console.ReadLine();
}
}
public struct Buffer72 {
public unsafe fixed byte bs[7];
}
您可以在此示例中任意访问堆栈帧。标准缓冲区溢出注入 技术可用于恶意代码来修补功能 返回地址并强制您的代码跳转到任意位置。
是的,那是非常不安全的。
Simon White提出了一个有效的观点:
我会选择“为编译器添加复杂性”。编译器必须检查没有.NET特定功能应用于应用于可枚举项的结构。例如,泛型,接口实现,甚至非原始数组的更深层属性等等。毫无疑问,运行时也会遇到一些互操作问题。
和Ibasa:
“但这已经由编译器完成了。”只是部分。编译器可以执行检查以查看是否管理了类型,但是不会生成用于将结构读/写到固定缓冲区的代码。它可以完成(没有什么能阻止它在CIL级别)它只是没有在C#中实现。
最后,Mehrdad:
我认为这实际上是因为他们不希望您使用固定大小的缓冲区(因为他们希望您使用托管代码)。使得与本机代码互操作变得太容易使得您不太可能将.NET用于所有内容,并且他们希望尽可能地推广托管代码。
答案似乎是一个响亮的“它只是没有实现”。
我的猜测是成本和实施时间对他们来说不值得。开发人员宁愿通过非托管代码推广托管代码。它可能在未来的C#版本中完成,但目前的CLR缺乏很多所需的复杂性。
替代方案可能是安全问题。由于固定缓冲区非常容易受到各种问题和安全风险的影响,如果它们在代码中实现得很差,我可以看到为什么不鼓励使用它们而不是C#中的托管代码。为什么要把很多工作放在你想阻止使用的东西上?
答案 2 :(得分:0)
我理解你的观点......另一方面,我认为它可能是微软保留的某种向前兼容性。您的代码被编译为MSIL,并且特定的.NET Framework和OS是将其布局在内存中。
我可以想象它可能来自intel的新CPU,它需要将变量布局到每8个字节以获得最佳性能。在这种情况下,将来需要.NET Framework 6和未来的Windows 9来以不同的方式布局这些结构。在这种情况下,您的示例代码将迫使Microsoft不要在将来更改内存布局,而不是将.NET框架加速到现代HW。
这只是推测......
您是否尝试过设置FieldOffset?见C++ union in C#