3

I need to read/write a bunch of structs to/from native memory. And I am trying to figure out whether or not should I bother with struct alignment. Here is a simple code I wrote to test things out. It writes a packed struct to unaligned pointer, and then reads the struct back:

public static unsafe class Program
{
    public static void Main()
    {
        Console.WriteLine(sizeof(MyStructPacked)); //returns 6, as expected (no padding) 

        var nativeMemory = Marshal.AllocHGlobal(33);

        var ptr = (byte*)nativeMemory.ToPointer();
        Console.WriteLine((int)ptr % 8 == 0); //true, pointer is aligned to 8 bytes
        Console.WriteLine((int)ptr % 16 == 0); //true, pointer is also aligned to 16 bytes
        
        Unsafe.InitBlock(ptr, 0, 33);
        //!!! aligned write to unaligned pointer
        Unsafe.Write(ptr + 3, new MyStructPacked{Short = ushort.MaxValue / 2, Int = UInt32.MaxValue});
        for (int i = 0; i < 10; i++)
        {
            byte b = *(ptr + i);
            //returns 0 0 0 255 127 255 255 255 255 0 (which is correct)
            Console.WriteLine(b);
        }
        
        //!!! aligned read from unaligned pointer
        var read1 = Unsafe.Read<MyStructPacked>(ptr + 3);
        //unaligned read from unaligned pointer
        var read2 = Unsafe.ReadUnaligned<MyStructPacked>(ptr + 3);
        //!!! dereferencing unaligned pointer
        var read3 = *(MyStructPacked*)(ptr + 3);
        
        //all produce the same (correct) result.
        Console.WriteLine(read1.Int + " " + read1.Short);
        Console.WriteLine(read2.Int + " " + read2.Short);
        Console.WriteLine(read3.Int + " " + read3.Short);

        //!!! aligned read from unaligned pointer
        var readInt1 = Unsafe.Read<uint>(ptr + 5);
        //unaligned read from unaligned pointer
        var readInt2 = Unsafe.ReadUnaligned<uint>(ptr + 5);
        //!!! dereferencing unaligned pointer
        var readInt3 = *(uint*)(ptr + 5);
        
        //all return uint.MaxValue (also correct)
        Console.WriteLine(readInt1);
        Console.WriteLine(readInt2);
        Console.WriteLine(readInt3);

        Marshal.FreeHGlobal(nativeMemory);
    }
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MyStructPacked
{
    public ushort Short;
    public uint Int;
}

I tested this on a couple of systems I have at hand (with .Net 6.0), and it works on them without a problem. The spec says value types have to be aligned a certain way, but I can't find any info on what actually happens if pointers aren't aligned the way they should according to spec. The best I could find is that it is considered "undefined behavior" (mentioned here), which is not very helpful.

So my question is, how safe is above code? Can I expect it to work on other .Net-supported systems and platforms? Or is it working purely by chance? If it won't work on some systems than what is the worst that can happen? Can this code crash? Is there anything I can do to reproduce potential problems (maybe run it on specific system?)? Any advice here is welcome, thanks.

P.S. I am aware of performance implications of unaligned access, but those are of secondary concern. At the moment the main concern is execution safety.

Nikita B
  • 3,303
  • 1
  • 23
  • 41
  • The only constraint seems to be that [Arrays (and strings?) must start on 4-byte boundaries](https://stackoverflow.com/a/6892012/361177). Otherwise, did you expect to have the code executed on *spicy* architectures ? (ARM ?) – Orace Jul 09 '22 at 17:19
  • Potentially, yes. – Nikita B Jul 09 '22 at 17:39
  • These struct layout restrictions are interesting. I was only planning to use structs that are fully blittable (so only primitive numeric fields, as in my example), but I will try to play around with reference fields, just to see if it breaks things. – Nikita B Jul 09 '22 at 17:39
  • 2
    https://github.com/dotnet/runtime/issues/1650#issuecomment-573907109? – GSerg Jul 09 '22 at 17:44
  • Good read, thanks – Nikita B Jul 09 '22 at 18:17
  • c# was designed to minimize computer crashing and going to blue screen. There are checks that have been added to c# to prevent blue screen which often happens when you access wrong areas of memory. Unsafe mode in c# eliminates these checks for pointers access wrong areas of memory. When a program gets compiled in c# memory areas are assigned so when you access unassigned areas exception will occur instead of crashing the PC (blue screen). Sometimes these exception will occur at end of memory areas due to memory not being aligned properly. The type of micro being used can affect alignment. – jdweng Jul 09 '22 at 18:41
  • "Undefined behaviour" is undefined. That means anything could happen, so if you want to guarantee safety then don't do it. Why is it necessary? – Charlieface Jul 10 '22 at 02:33

1 Answers1

1

I ran into issues on armv7 processors, those are relatively common in low-end sector. They do in fact throw actual exceptions if you try to de-reference unaligned pointer (using safe ref operator or unsafe *). The issue is easy to reproduce and exception message is fairly explicit.

So in the end I had to manually align all unmanaged memory which had to be accessed by reference. It was tedious but, well... manageable. If you do not need to access the memory by reference, Unsafe.ReadUnaligned and Unsafe.WriteUnaligned methods prevent exceptions as well (at the cost of access speed, obviously).

Nikita B
  • 3,303
  • 1
  • 23
  • 41