为什么固定大小的缓冲区只能是原始类型?

时间:2013-09-16 23:38:03

标签: c# unsafe

我们必须与本机代码交互很多,在这种情况下,使用不需要编组的不安全结构会快得多。但是,当结构包含非原始类型的固定大小缓冲区时,我们无法执行此操作。 为什么C#编译器要求固定大小的缓冲区只是原始类型?为什么固定大小的缓冲区不能由结构组成,例如:

[StructLayout(LayoutKind.Sequential)]
struct SomeType
{
  int Number1;
  int Number2;
}

3 个答案:

答案 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#