8
votes

I am used a structure to represent pure data. One of the fields is a fixed-size buffer, as shown below.

[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 { ... } }

    ...
}

Within the get/set functions I tried to do the following but I get "Compiler Error CS1666: You cannot use fixed size buffers contained in unfixed expressions. Try using the fixed statement."

return this._e_res[0];

However, the following work:

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

ImageDosHeader local = this;
return local._e_res[0];

I can easily use the workarounds, however, I am wondering why directly accessing the fixed-size buffer from this is illegal. Or is this a bug that I should report?

I am using .NET 2.0.

2

2 Answers

12
votes

It's because of the underlying IL instructions.

The program does this sequence of instructions to get the element you want:

  1. Load the address onto the stack.

  2. Load the offset onto the stack.

  3. Add them.

  4. Read the value at that memory address.

If the object is in the heap and then moves because of garbage collection before step 4, then the address loaded from step 1 will no longer be valid. To protect against this, you need to pin the object into memory first.

(The fact that you're accessing the structure through the this pointer means that you have no idea if the structure is on the heap or on the stack, so you have to pin it just in case it's on the heap.)

The second example works because it copies the structure to the stack, and so the copy can never move around, so the address will always be valid.

Why doesn't the same issue happen with other kinds of fields? Because their offset is known at compile-time, whereas the array index is known at run-time, so the JIT can generate code that will always access the fields correctly.

6
votes

The perspective from which you look at the fixed keyword changes its semantics, which is rather confusing. The fixed's statement original purpose has been to pin a piece of blittable memory in place, in C# 2.0 it is used along with a field declaration to denote that 'the array is exactly N elements long', thus, of fixed size, not fixed in memory.

I'd get rid of the fixed keyword in field declaration and just use:

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

This way, the struct is still blittable and not a pain in the butt to work with.