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

8

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

[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。

2个回答

12
这是因为底层IL指令的缘故。
程序执行以下指令序列以获取所需元素:
  1. 将地址加载到堆栈。
  2. 将偏移量加载到堆栈。
  3. 将它们相加。
  4. 读取该内存地址处的值。
如果对象在堆中并且在第4步之前由于垃圾回收而移动,则从第1步加载的地址将不再有效。为了防止这种情况发生,您需要先将对象固定到内存中。
(通过“this”指针访问结构的事实意味着您不知道结构体是在堆上还是在栈上,因此必须对其进行固定以防万一它位于堆中。)
第二个示例可行是因为它将结构体复制堆栈中,因此副本永远不会移动,因此地址始终有效。
为什么其他类型的字段没有同样的问题?因为它们的偏移量在编译时就已经确定了,而数组索引在运行时才知道,因此JIT可以生成始终正确访问字段的代码。

1
谢谢!由于您对第二种方法(复制到本地变量)的评论,我意识到该方法根本无法用于“set”方法。 - coderforlife

6
从不同的角度来看待fixed关键字会改变其语义,这相当令人困惑。在C# 2.0中,fixed关键字最初的目的是将一个可复制的内存固定在一个位置上,它与字段声明一起使用以表示“数组恰好有N个元素”,因此是固定大小的,而不是固定在内存中。
我建议在字段声明中摒弃fixed关键字,只需使用:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] private ushort[] _e_res;

这样做,结构体仍然是可平置的,并且不难处理。


不要尝试获取结构的地址。 ;) - user541686
关键是一开始不要获取结构体的地址,因为这并不需要。这段代码允许 OP 调用:return this._e_res[0]; 同时保留可平坦化语义。如果您明确需要地址,只需使用 GCHandle 的用途即可。 - arul
@arul:是的,如果你只是在处理单个结构体,那么你不需要地址。但是,如果你正在处理一个结构体数组,使用.NET数组之前最好三思而后行。 :) (另外,GCHandle在这里也不是一个好主意——它会执行装箱操作。) - user541686
你在这里犯了错误。ushort是可平坦化类型,因此,该类型的任何一维数组也隐式地可平坦化。此外,如果该类型是可平坦化的,则在编组期间它会被固定而不是复制。 - arul
1
@arul:数组本身确实是可平坦化的,但结构体不行。 - user541686
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接