这里有一个简单的问题:有没有办法从锯齿状数组转换为双指针?
例如将 a 转换double[][]
为double**
不幸的是,这不能仅仅通过强制转换来完成(就像在普通的旧 C 中一样),不幸的是。使用fixed
语句似乎也不能解决问题。在 C# 中是否有任何(最好尽可能高效)方法来完成此任务?我怀疑解决方案可能根本不是很明显,尽管我希望有一个简单的解决方案。
这里有一个简单的问题:有没有办法从锯齿状数组转换为双指针?
例如将 a 转换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方法。
首先,做什么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));
}
}
}
}
}
}
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
}
}
我不禁想知道这是为了什么,以及是否有比需要双指针更好的解决方案。
我暂时使用了 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;
}
}