Search code examples
c#structstructlayout

unassigned field on explicit struct layout


I want to produce a C# sqrt benchmark, but some sqrt functions require an union for bitwise computation.

My union is defined as it :

[StructLayout(LayoutKind.Explicit)]
struct U
{
    [FieldOffset(0)]
    public int i;
    [FieldOffset(0)]
    public float x;
}

and next code produces an unassigned field error on u.i :

U u;
u.x = x;
u.i = (1 << 29) + (u.i >> 1) - (1 << 22);

I know u.i was assigned when u.x is also assigned, so is it possible to ignore an unassigned field error at compile time without an explicit u.i assign?


Solution

  • FieldOffset is primarily an interop feature; it tells how the runtime should marshal the structure when used in a native context. In some cases (blittable structures), it also affects the managed memory layout. It should be noted that any explicit structure layout will mean the code is no longer portable. That may or may not be a problem for your code.

    The important part is that the compiler does not even try to verify unsafe code beyond a few very simple heuristics; it still sees two fields, and you're never assigning one of the fields, so you're violating the struct contract. This is not much different from using e.g. pointer arithmetic to access the field. It's still much more likely that you made a mistake and forgot to assign a field than that this is explicitly what you want. If you really want to, you can just do an assignment (or use a constructor; C# isn't C, and U u = new U(); is usually perfectly fine) before reading the field.

    But in your case, there's little reason to use a union field anyway. If you want to do unmanaged operations like this, use unsafe code. That's what it's for. Don't abuse interop features. And of course, whichever way you choose, don't expect it to be portable.

    float x = 42.0f;
    
    (*(int*)((void*)&x)) // The value of `x` reinterpreted as an int
    

    A full example of your sqrt approximation might look something like this:

    unsafe void Main()
    {
      sqrt(42).Dump(); // 6.625
      sqrt(9).Dump();  // 3.125
    }
    
    unsafe float sqrt(float x)
    {
      int* pX = (int*)((void*)&x);
    
      *pX = (1 << 29) + (*pX >> 1) - (1 << 22);
    
      return x;
    }