简单的问题:是否有办法将锯齿数组转换为双指针?
例如,将double[][]
转换成 double**
遗憾的是,这不能像在普通的C语言中那样只需进行强制转换就能完成。使用fixed
语句似乎也无法解决问题。是否有任何(最好是尽可能高效的)方法来在C#中实现这一点?我怀疑解决方案可能并不是非常明显,但我仍然希望有一个简单明了的解决方法。
简单的问题:是否有办法将锯齿数组转换为双指针?
例如,将double[][]
转换成 double**
遗憾的是,这不能像在普通的C语言中那样只需进行强制转换就能完成。使用fixed
语句似乎也无法解决问题。是否有任何(最好是尽可能高效的)方法来在C#中实现这一点?我怀疑解决方案可能并不是非常明显,但我仍然希望有一个简单明了的解决方法。
一点点安全。
如第一个解决方案的评论中所提到的,嵌套数组可以被移动,因此它们也应该被固定。
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
做了什么?它在内部系统表中创建一个新条目,该表具有对作为参数传递给方法的对象的引用。因此,当垃圾收集器检查堆时,它将检查条目的内部表,如果找到一个条目,它将将对象标记为活动对象,并且不会将其移出堆。然后,它将查看如何固定此对象,并且在紧缩阶段不会移动内存中的对象。fixed
语句几乎执行相同的操作,只是在离开作用域时自动“取消固定”对象。fixed
固定的每个对象都会在离开作用域时自动“取消固定”。在我们的情况下,这将在下一次循环迭代中发生。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));
}
}
}
}
}
}
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
}
}
我不禁想知道这是干嘛的,是否有比要求双指针更好的解决方案。
arrayofptr[i]
时才固定了指针。这意味着当你使用指针时数组可以移动,这可能会破坏内存并导致不可预测的错误。 - svick目前我选择了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;
}
}
fixed
代码块之外使用它,因为对象可能会在此期间移动。 - svickMarshal
静态方法之一可以使其更加健壮。 - Noldorin