Преобразование из зубчатого массива в двойной указатель в С#

Простой вопрос здесь: есть ли способ конвертировать из массива с зубчатым контуром в двойной указатель?

например. Преобразуйте a double[][] в double**

Это не может быть сделано, например, путем кастинга, к сожалению (как это может быть в обычном старом C). Использование инструкции fixed также не помогает решить проблему. Есть ли способ (желательно как можно более эффективный) выполнить это на С#? Я подозреваю, что решение может быть не совсем очевидным, хотя я надеюсь на простой, тем не менее.

Ответ 1

Двойной [] [] является массивом 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
    }
}

Я не могу не задаться вопросом, для чего это необходимо, и если есть лучшее решение, чем требование двойного указателя.

Ответ 2

Немного безопасности.
Как упоминалось в комментариях к первому решению, вложенные массивы можно перемещать, поэтому их также следует закреплять.

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

Краткое объяснение проблемы:

Когда мы размещаем некоторые объекты в куче, они могут быть перемещены в другое место при сборе мусора. Итак, представьте себе следующую ситуацию: вы выделили некоторый объект и ваши внутренние массивы, все они помещены в кучу с нулевым поколением.

enter image description here

Теперь какой-то объект вышел из области видимости и стал мусором, некоторые объекты просто были выделены. Сборщик мусора будет перемещать старые объекты из кучи и перемещать другие объекты ближе к началу или даже к следующему поколению, сжимая кучу. Результат будет выглядеть так:

enter image description here

Итак, наша цель - "закрепить" некоторые объекты в куче, чтобы они не двигались. Что нам нужно для достижения этой цели? Мы исправили оператор и метод 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));
                    }
                }
            }
        }
    }
}

Ответ 3

Я пошел с решением 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;
    }
}