在C#中从锯齿状数组转换为双指针

Nol*_*rin 14 c# arrays pointers jagged-arrays double-pointer

这里有一个简单的问题:有没有办法从锯齿状数组转换为双指针?

例如,转换double[][]double**

不幸的是,这不能仅仅通过铸造(就像它可以在普通的旧C中)一样.使用fixed语句似乎也无法解决问题.在C#中是否有任何(最好尽可能高效)的方法来实现这一目标?我怀疑解决方案可能不是很明显,尽管我希望尽管是直截了当的.

cas*_*rad 7

一点安全.
正如对第一个解决方案的评论中所提到的,嵌套数组可以移动,因此它们也应该被固定.

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();
}
Run Code Online (Sandbox Code Playgroud)

问题的简要说明:

当我们在堆上分配一些对象时,它们可以被移动到垃圾收集的另一个位置.所以,想象下一种情况:你已经分配了一些对象和你的内部数组,它们都被放置在堆上的零代.

在此输入图像描述

现在,一些对象已从范围变为垃圾,一些对象刚刚被分配.垃圾收集器会将旧对象移出堆中,并将其他对象移动到更靠近乞讨的地方,甚至移动到下一代,压缩堆.结果如下:

在此输入图像描述

因此,我们的目标是"固定"堆中的一些对象,这样它们就不会移动.我们必须实现这一目标?我们有固定的语句和GCHandle.Allocate方法.

首先,做什么GCHandle.Allocate?它在内部系统表中创建新条目,该条目具有对作为参数传递给方法的对象的引用.因此,当垃圾收集器检查堆时,他将检查内部表中的条目,如果他找到一个,他会将对象标记为活动并且不会将其移出堆.然后,他将查看该对象是如何固定的,并且不会在压缩阶段将对象移动到内存中.fixed语句几乎相同,只是当你离开范围时它会自动"取消"对象.

总结:每个被固定的对象在fixed离开范围后将自动"取消固定".在我们的例子中,它将在循环的下一次迭代中.

如何检查您的对象不会被移动或垃圾收集:只消耗所有堆的预算以进行零生成并强制GC压缩堆.换句话说:在堆上创建大量对象.在固定物体或"固定"它们之后再进行操作.

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();
Run Code Online (Sandbox Code Playgroud)

小注意:有两种类型的堆 - 用于大型物体和小型物体.如果对象很大,则应创建大对象来检查代码,否则小对象不会强制GC启动垃圾收集和压缩.

最后,这里有一些示例代码,演示了使用未固定/未固定指针访问底层数组的危险 - 对于任何感兴趣的人.

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));
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


bsn*_*eze 4

double [] []是double []的数组,而不是double*,所以要得到一个双**,我们首先需要一个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
    }
}
Run Code Online (Sandbox Code Playgroud)

我不禁想知道这是为了什么,以及是否有一个比需要双指针更好的解决方案.

  • 你确定这会有效吗?在我看来,只有当你将它们分配给`arrayofptr [i]`时才能固定内部数组.这意味着当你使用它的指针时,数组可以移动,这可能会破坏内存并导致不可预测的错误. (4认同)
  • [示例](http://ideone.com/RsuSIc)代码证明,如果某人感兴趣,如果内部数组被移动,@ bsneeze的代码可能会导致错误. (2认同)