固定大小的缓冲区不能直接从“this”对象使用

我用一种结构来表示纯数据。 其中一个字段是固定大小的缓冲区,如下所示。

[StructLayout(LayoutKind.Sequential, Pack=2)] unsafe struct ImageDosHeader { ... private fixed ushort _e_res[4]; ... [Description("Reserved")] [DisplayName("e_res[0]")] public ushort e_res_0 { get { ... } set { ... } } ... } 

在get / set函数中,我尝试执行以下操作,但是我得到“编译器错误CS1666:您不能使用未固定表达式中包含的固定大小缓冲区。请尝试使用fixed语句。”

 return this._e_res[0]; 

但是,以下工作:

 fixed (ImageDosHeader* p = &this) return p->_e_res[0]; ImageDosHeader local = this; return local._e_res[0]; 

我可以轻松地使用变通方法,但是,我想知道为什么直接从这里访问固定大小的缓冲区是非法的。 或者这是我应该报告的错误?

我使用的是.NET 2.0。

这是因为底层的IL指令。

程序执行此序列指令以获取所需的元素:

  1. 将地址加载到堆栈中。

  2. 将偏移量加载到堆栈上。

  3. 添加它们。

  4. 读取该内存地址的值。

如果对象在堆中,然后在步骤4之前由于垃圾回收而移动,则从步骤1加载的地址将不再有效。 为了防止这种情况,您需要先将对象固定到内存中。

(你通过this指针访问结构的事实意味着你不知道结构是在堆上还是在堆栈上,所以你必须将它固定在堆上。)

第二个示例有效,因为它将结构复制堆栈 ,因此副本永远不会移动,因此地址始终有效。

为什么其他类型的字段不会出现同样的问题? 因为它们的偏移量在编译时是已知的,而数组索引在运行时是已知的,因此JIT可以生成始终正确访问字段的代码。

从中查看fixed关键字的视角会改变其语义,这是相当混乱的。 fixed声明的最初目的是将一段闪烁的存储器固定到位,在C#2.0中,它与字段声明一起用于表示“数组正好是N个元素长”,因此具有固定大小,而不是固定在内存中。

我在字段声明中删除了fixed关键字,只需使用:

 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] private ushort[] _e_res; 

这样,结构仍然是快速的,而不是一个痛苦的工作。