在 C# 中从交错数组转换为双指针

发布于 2024-07-21 07:45:15 字数 253 浏览 6 评论 0原文

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

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

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

Simple question here: is there any way to convert from a jagged array to a double pointer?

e.g. Convert a double[][] to double**

This can't be done just by casting unfortunately (as it can in plain old C), unfortunately. Using a fixed statement doesn't seem to resolve the problem either. Is there any (preferably as efficient as possible) way to accomplish this in C#? I suspect the solution may not be very obvious at all, though I'm hoping for a straightforward one nonetheless.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

虐人心 2024-07-28 07:45:15

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

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

A little bit of safety.
As mentioned in comments to the first solution, nested arrays could be moved, so they should be pinned too.

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

A brief explanation of the problem:

When we allocate some objects on the heap, they could be moved to another location on garbage collecting. So, imagine next situation: you have allocated some object and your inner arrays, they are all placed in zero generation on the heap.

enter image description here

Now, some object has gone from scope and became garbage, some objects just been allocated. Garbage collector will move old objects out of heap and move other objects closer to the beginning or even to the next generation, compacting heap. The result will looks like:

enter image description here

So, our goal is to “pin” some objects in heap, so they would not move.
What we have to achieve this goal? We have fixed statement and GCHandle.Allocate method.

First, what GCHandle.Allocate does? It creates new entry in inner system table that have a reference to the object that passed to method as a parameter. So, when garbage collector will examine heap, he will check inner table for entries and if he will find one, he will mark object as alive and will not move it out of heap. Then, he will look on how this object is pinned and will not move the object in memory in compacting stage. fixed statement does almost the same, except it “unpins” object automatically when you leave scope.

Summarizing: each object that has been pinned with fixed will be automatically “unpinned” once he left a scope. In our case, it will be on next iteration of loop.

How to check that your objects will not be moved or garbage collected: just consume all the heap's budget for zero generation and force GC to compact heap. In other words: create a lot of objects on the heap. And do it after you pinned your objects or “fixed” them.

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

Small notice: there are two types of heaps — for large objects and for small ones. If your object is large, you should create large objects to check your code, otherwise small objects will not force GC to start garbage collection and compacting.

Lastly, here's some sample code, demonstrating the dangers of accessing the underlying arrays with unpinned/unfixed pointers - for anybody who is interested.

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));
                    }
                }
            }
        }
    }
}
随心而道 2024-07-28 07:45:15

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

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

A double[][] is an array of double[], not of double* , so to get a double** , we first need a 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
    }
}

I can't help but wonder what this is for and if there is a better solution than requiring a double-pointer.

猥琐帝 2024-07-28 07:45:15

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

I've gone with zachrrs solution for the time being (which was what I was suspecting might need to be done in the first place). Here it is an extension method:

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文