Converting from a jagged array to double pointer i

2019-01-18 11:39发布

Simple question here: is there any way to convert from a jagged array to a double pointer?

e.g. Convert a double[][] to double**

This can't be done just by casting unfortunately (as it can in plain old C), unfortunately. Using a fixed statement doesn't seem to resolve the problem either. Is there any (preferably as efficient as possible) way to accomplish this in C#? I suspect the solution may not be very obvious at all, though I'm hoping for a straightforward one nonetheless.

3条回答
The star\"
2楼-- · 2019-01-18 11:53

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;
    }
}
查看更多
在下西门庆
3楼-- · 2019-01-18 11:54

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.

enter image description here

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 begging or even to the next generation, compacting heap. The result will looks like:

enter image description here

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));
                    }
                }
            }
        }
    }
}
查看更多
霸刀☆藐视天下
4楼-- · 2019-01-18 11:55

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.

查看更多
登录 后发表回答