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

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

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

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

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 } } 

我不禁想知道这是什么,如果有一个比需要双指针更好的解决方案。

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

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

我暂时使用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; } }