Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a Span<T> pointing to Fixed Sized Buffers without a fixed expression possible?

I'm using .NET Core 2.1 and language standard 7.3. I wish to reference a fixed buffer without obtaining a pointer to it. Is it currently possible?

public unsafe struct InteropStruct
{
    private fixed byte dataField[32];

    public Span<byte> Data
    {
        get
        {
            //return a span referencing the private field without a fixed statement
        }
    }
}

I know Span is currently able to track a managed array through garbage collections, so I don't see anything preventing it from tracking fixed buffers in a similar fashion.

If its not possible, what would happen if I did use a fixed statement like such:

public unsafe struct InteropStruct
{
    private fixed byte dataField[32];

    public Span<byte> Data
    {
        get
        {
            fixed (byte* ptr = dataField)
            {
                return new Span<byte>(ptr, 32);
            }
        }
    }
}

Would the garbage collector become a problem if the struct is wrapped inside an object or class on the heap?

like image 298
Gamma_Draconis Avatar asked Jan 21 '19 08:01

Gamma_Draconis


People also ask

When to use span T?

You can use Span<T> to wrap an entire array. Because it supports slicing, it can not only point to the first element of the array, but any contiguous range of elements within the array. foreach (int i in slice) Console. WriteLine($"{i} ");

What is span T?

A Span<T> represents a contiguous region of arbitrary memory. A Span<T> instance is often used to hold the elements of an array or a portion of an array. Unlike an array, however, a Span<T> instance can point to managed memory, native memory, or memory managed on the stack.

What is a fixed size buffer?

A fixed size buffer is a member that represents storage for a fixed length buffer of variables of a given type. A fixed size buffer declaration introduces one or more fixed size buffers of a given element type. Fixed size buffers are only permitted in struct declarations and can only occur in unsafe contexts.

What is the aim of the span type c#?

Span<T> is a new value type at the heart of . NET. It enables the representation of contiguous regions of arbitrary memory, regardless of whether that memory is associated with a managed object, is provided by native code via interop, or is on the stack.


1 Answers

So, I've done some research in the form of ILSpy'ing .NET Assemblies, and some testing on .NET Core 2.1. My Test results are as follows:

    interface ITest
    {
        Span<byte> Data { get; }
    }

    unsafe struct TestStruct : ITest
    {
        fixed byte dataField[8];

        public Span<byte> Data
        {
            get
            {
                //Unsafe.AsPointer() to avoid the fixed expression :-)
                return new Span<byte>(Unsafe.AsPointer(ref dataField[0]), 8);
            }
        }
    }

    class Program
    {
        //Note: This test is done in Debug mode to make sure the string allocation isn't ommited
        static void Main(string[] args)
        {
            new string('c', 200);

            //Boxes the struct onto the heap.
            //The object is allocated after the string to ensure it will be moved during GC compacting
            ITest HeapAlloc = new TestStruct();

            Span<byte> span1, span2;

            span1 = HeapAlloc.Data; //Creates span to old location

            GC.Collect(2, GCCollectionMode.Forced, true, true); //Force a compacting garbage collection

            span2 = HeapAlloc.Data; //Creates span to new location

            //Ensures that if the pointer to span1 wasn't updated, that there wouldn't be heap corruption
            //Write to Span2
            span2[0] = 5;
            //Read from Span1
            Console.WriteLine(span1[0] == 5); //Prints true in .NET Core 2.1, span1's pointer is updated
        }
    }

What I've learned from my research into the IL, please forgive me if I'm not explaining this correctly:

.NET Core's 2 Field Span:

//Note, this is not the complete declaration, just the fields
public ref readonly struct Span<T>
{
    internal readonly ByReference<T> _pointer;
    private readonly int _length;
}

.NET Framework's 3 Field Span:

//Same note as 2 Field Span
public ref readonly struct Span<T>
{
    private readonly Pinnable<T> _pinnable;
    private readonly IntPtr _byteOffset;
    private readonly int _length;
}

.Net Core is using the 2 field model of Span. Due to the .NET Framework using the 3 field model, It's pointer will not be updated. The reason? The Span<T>(void* pointer, int length) constructor (which I am using for this) for the 3 field span sets the _byteOffset field with the pointer argument. The pointer in the 3 field span that would be updated by the GC is the _pinnable field. With the 2 field Span, they are the same.

So, the answer to my question is, yes I can have a Span point to a fixed buffer with or without a fixed statement, but its dangerous to do this at all when not using .NET Core's 2 field Span Model. Correct me if I'm wrong about .NET Framework's current Span model.

like image 103
Gamma_Draconis Avatar answered Oct 10 '22 12:10

Gamma_Draconis