Nikita B
Nikita B

Reputation: 3333

How safe are unaligned pointers in modern .Net?

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.

Upvotes: 3

Views: 777

Answers (1)

Nikita B
Nikita B

Reputation: 3333

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).

Upvotes: 1

Related Questions