14

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

例如将 a 转换double[][]double**

不幸的是,这不能仅仅通过强制转换来完成(就像在普通的旧 C 中一样),不幸的是。使用fixed语句似乎也不能解决问题。在 C# 中是否有任何(最好尽可能高效)方法来完成此任务?我怀疑解决方案可能根本不是很明显,尽管我希望有一个简单的解决方案。

4

3 回答 3

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();
}

问题的简要说明:

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

在此处输入图像描述

现在,一些对象已经从作用域中消失并变成了垃圾,一些对象刚刚被分配。垃圾收集器会将旧对象移出堆,并将其他对象移到更靠近开始甚至下一代的位置,从而压缩堆。结果将如下所示:

在此处输入图像描述

因此,我们的目标是将一些对象“固定”在堆中,这样它们就不会移动。我们需要什么来实现这个目标?我们有固定的语句和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();

小提示:有两种类型的堆——用于大型对象和小型对象。如果你的对象很大,你应该创建大对象来检查你的代码,否则小对象不会强制 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));
                    }
                }
            }
        }
    }
}
于 2015-12-09T17:30:21.420 回答
4

double[][] 是 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
    }
}

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

于 2009-05-20T21:06:36.033 回答
-5

我暂时使用了 zachrrs 解决方案(这是我怀疑可能首先需要完成的)。这是一个扩展方法:

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;
    }
}
于 2009-05-20T21:42:03.823 回答