我用一种结构来表示纯数据.其中一个字段是固定大小的缓冲区,如下所示.
[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 { ... } }
...
}
Run Code Online (Sandbox Code Playgroud)
在get/set函数中,我尝试执行以下操作,但是我得到"编译器错误CS1666:您不能使用包含在未固定表达式中的固定大小缓冲区.请尝试使用fixed语句."
return this._e_res[0];
Run Code Online (Sandbox Code Playgroud)
但是,以下工作:
fixed (ImageDosHeader* p = &this)
return p->_e_res[0];
ImageDosHeader local = this;
return local._e_res[0];
Run Code Online (Sandbox Code Playgroud)
我可以轻松地使用变通方法,但是,我想知道为什么直接从这里访问固定大小的缓冲区是非法的.或者这是我应该报告的错误?
我使用的是.NET 2.0.
Meh*_*dad 11
这是因为底层的IL指令.
程序执行此序列指令以获取所需的元素:
将地址加载到堆栈中.
将偏移量加载到堆栈上.
添加它们.
读取该内存地址的值.
如果对象在堆中,然后在步骤4之前由于垃圾回收而移动,则从步骤1加载的地址将不再有效.为了防止这种情况,您需要先将对象固定到内存中.
(您通过this指针访问结构的事实意味着您不知道结构是在堆上还是堆栈上,因此您必须将其固定以防万一它在堆上.)
第二个示例有效,因为它将结构复制到堆栈,因此副本永远不会移动,因此地址始终有效.
为什么其他类型的字段不会出现同样的问题?因为它们的偏移量在编译时是已知的,而数组索引在运行时是已知的,所以JIT可以生成始终正确访问字段的代码.
从中查看fixed关键字的视角会改变其语义,这是相当混乱的.该fixed声明的最初目的是固定存储器,在C#2.0中,它与字段声明一起使用,表示'数组正好是N个元素长',因此,固定大小,不固定在记忆中.
我fixed在字段声明中删除了关键字,只使用:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] private ushort[] _e_res;
Run Code Online (Sandbox Code Playgroud)
通过这种方式,结构仍然是快速的,而不是一个痛苦的工作.