Converting from a jagged array to double pointer in C#

六眼飞鱼酱① 提交于 2019-11-28 09:09:08

A double[][] is an array of double[], not of double* , so to get a double** , we first need a double*[]

double[][] array = //whatever
//initialize as necessary

fixed (double* junk = &array[0][0]){

    double*[] arrayofptr = new double*[array.Length];
    for (int i = 0; i < array.Length; i++)
        fixed (double* ptr = &array[i][0])
        {
            arrayofptr[i] = ptr;
        }

    fixed (double** ptrptr = &arrayofptr[0])
    {
        //whatever
    }
}

I can't help but wonder what this is for and if there is a better solution than requiring a double-pointer.

cassandrad

A little bit of safety.
As mentioned in comments to the first solution, nested arrays could be moved, so they should be pinned too.

unsafe
{
    double[][] array = new double[3][];
    array[0] = new double[] { 1.25, 2.28, 3, 4 };
    array[1] = new double[] { 5, 6.24, 7.42, 8 };
    array[2] = new double[] { 9, 10.15, 11, 12.14 };

    GCHandle[] pinnedArray = new GCHandle[array.Length];
    double*[] ptrArray = new double*[array.Length];

    for (int i = 0; i < array.Length; i++)
    {
        pinnedArray[i] = GCHandle.Alloc(array[i], GCHandleType.Pinned);
    }

    for (int i = 0; i < array.Length; ++i)
    {
        // as you can see, this pointer will point to the first element of each array
        ptrArray[i] = (double*)pinnedArray[i].AddrOfPinnedObject();
    }

    // here is your double**
    fixed(double** doublePtr = &ptrArray[0])
    {
        Console.WriteLine(**doublePtr);
    }

    // unpin all the pinned objects,
    // otherwise they will live in memory till assembly unloading
    // even if they will went out of scope
    for (int i = 0; i < pinnedArray.Length; ++i)
        pinnedArray[i].Free();
}

A brief explanation of the problem:

When we allocate some objects on the heap, they could be moved to another location on garbage collecting. So, imagine next situation: you have allocated some object and your inner arrays, they are all placed in zero generation on the heap.

Now, some object has gone from scope and became garbage, some objects just been allocated. Garbage collector will move old objects out of heap and move other objects closer to the beginning or even to the next generation, compacting heap. The result will looks like:

So, our goal is to “pin” some objects in heap, so they would not move. What we have to achieve this goal? We have fixed statement and GCHandle.Allocate method.

First, what GCHandle.Allocate does? It creates new entry in inner system table that have a reference to the object that passed to method as a parameter. So, when garbage collector will examine heap, he will check inner table for entries and if he will find one, he will mark object as alive and will not move it out of heap. Then, he will look on how this object is pinned and will not move the object in memory in compacting stage. fixed statement does almost the same, except it “unpins” object automatically when you leave scope.

Summarizing: each object that has been pinned with fixed will be automatically “unpinned” once he left a scope. In our case, it will be on next iteration of loop.

How to check that your objects will not be moved or garbage collected: just consume all the heap's budget for zero generation and force GC to compact heap. In other words: create a lot of objects on the heap. And do it after you pinned your objects or “fixed” them.

for(int i = 0; i < 1000000; ++i)
{
    MemoryStream stream = new MemoryStream(10);
    //make sure that JIT will not optimize anything, make some work
    stream.Write(new Byte[]{1,2,3}, 1, 2);
}
GC.Collect();

Small notice: there are two types of heaps — for large objects and for small ones. If your object is large, you should create large objects to check your code, otherwise small objects will not force GC to start garbage collection and compacting.

Lastly, here's some sample code, demonstrating the dangers of accessing the underlying arrays with unpinned/unfixed pointers - for anybody who is interested.

namespace DangerousNamespace
{
    // WARNING!
    // This code includes possible memory access errors with unfixed/unpinned pointers!
    public class DangerousClass
    {
        public static void Main()
        {
            unsafe
            {
                double[][] array = new double[3][];
                array[0] = new double[] { 1.25, 2.28, 3, 4 };
                array[1] = new double[] { 5, 6.24, 7.42, 8 };
                array[2] = new double[] { 9, 10.15, 11, 12.14 };

                fixed (double* junk = &array[0][0])
                {
                    double*[] arrayofptr = new double*[array.Length];
                    for (int i = 0; i < array.Length; i++)
                        fixed (double* ptr = &array[i][0])
                        {
                            arrayofptr[i] = ptr;
                        }

                    for (int i = 0; i < 10000000; ++i)
                    {
                        Object z = new Object();
                    }
                    GC.Collect();

                    fixed (double** ptrptr = &arrayofptr[0])
                    {
                        for (int i = 0; i < 1000000; ++i)
                        {
                            using (MemoryStream z = new MemoryStream(200))
                            {
                                z.Write(new byte[] { 1, 2, 3 }, 1, 2);
                            }
                        }
                        GC.Collect();
                        // should print 1.25
                        Console.WriteLine(*(double*)(*(double**)ptrptr));
                    }
                }
            }
        }
    }
}

I've gone with zachrrs solution for the time being (which was what I was suspecting might need to be done in the first place). Here it is an extension method:

public static double** ToPointer(this double[][] array)
{
    fixed (double* arrayPtr = array[0])
    {
        double*[] ptrArray = new double*[array.Length];
        for (int i = 0; i < array.Length; i++)
        {
            fixed (double* ptr = array[i])
                ptrArray[i] = ptr;
        }

        fixed (double** ptr = ptrArray)
            return ptr;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!