2009-05-20 2 views
12

Простой вопрос здесь: есть ли какой-либо способ преобразования из массива с jagged в двойной указатель?Преобразование из неровного массива в двойной указатель в C#

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

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

+0

Я думаю, этот вопрос сводится к: вы можете конвертировать из одного указателя (в объект) в указатель на указатель? – zvolkov

+4

Решение с использованием метода расширения ToPointer - это плохая идея, потому что тогда вы будете использовать указатель за пределами «фиксированной» области, до которого время выполнения .NET могло перемещать массив в другое место памяти. – Greg

+0

Умный дизайн Microsoft должен был учитывать: «Исправить» базовое свойство объектов массива, а затем сделать массивы конвертируемыми в указатели (соответствующие C/C++) без необходимости странного фиксированного «утверждения». Таким образом, когда вы преобразовываете массив в указатель, как часть преобразования, массив автоматически «фиксирует» себя бесконечно (поскольку невозможно предсказать, как долго может быть указатель). Я не могу придумать какой-либо другой функциональный, безопасный способ поддержки поддержки указателей на управляемом языке ООП, и я лично считаю, что Microsoft была довольно недальновидна, чтобы пропустить это. – Giffyguy

ответ

5

двойная [] [] является массивом двойной [], а не двойной *, поэтому, чтобы получить двойной **, мы сначала должны двойной * []

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

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

+0

К сожалению, я не могу избежать использования двойного указателя, поскольку я вызываю внешнюю функцию C, а C# не может автоматически сортировать массивы с зазубринами. – Noldorin

+0

Я отправлю это как можно скорее, кстати. Благодарю. – Noldorin

+0

Мне пришлось отредактировать это как 6 раз, чтобы обойти SO, желая разобрать * s как курсив. Окно предварительного просмотра и фактический пост были непоследовательны в их интерпретации ... – bsneeze

-6

На данный момент я ушел с решением 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; 
    } 
} 
+6

Вы не можете использовать указатель вне блока 'fixed', где вы его объявили, потому что объект мог перемещаться за это время. – svick

+0

@svick: Конечно, вы можете. Это может не работать все время. Как это бывает, в этом случае ... Может быть, один из статических методов «Маршал» помог бы сделать его более надежным. – Noldorin

+5

Ну, может быть, сейчас это работает. Но вы меняете одну строку в несвязанной или просто не поверите в один прекрасный день, и это не сработает. Ваш код ** неверен **. Тот факт, что в настоящее время он работает, в основном является несчастным случаем. – svick

4

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

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

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

Не могли бы вы отредактировать этот ответ, чтобы показать создание двойника **? Как вы упомянули в более раннем комментарии, массивы не смежны, поэтому вам нужно создать отдельный массив закрепленных указателей, а затем вернуть указатель на этот отдельный массив.Возможно, с помощью другого метода, чтобы отключить все позже, когда двойной ** больше не нужен. Фактически, это начинает казаться действительно хорошим кандидатом для методов расширения для System.Array. :) – Giffyguy

+0

@ Giffyguy хорошо, да, я могу, но я думаю, что это будет похоже на то, что мы отвечаем на другой вопрос. Необходимость создания непрерывных массивов субъективна для меня и может быть реализована несколькими способами, и эти способы будут зависеть от начальной цели и принимающей стороны. Но в любом случае я добавлю несколько «простых» реализаций такого расширения к моему ответу. – cassandrad

+0

@ Giffyguy теперь я понял, что я не понял вашу точку зрения. Ну, я отредактировал ответ, чтобы показать, где двойное **. Надеюсь, это то, что вы просили меня сделать. Впервые я подумал, что вы попросили меня написать класс, который мог бы получить зубчатый массив и преобразовать его в непрерывный массив с функцией GetAddr, чтобы вывести этот массив за пределы, с безопасностью потоков, управлением ресурсами, IDispossable и другими интерфейсами SafeHandleZeroOrMinusOneIsInvalid. Провел несколько часов, выполняя это, ха-ха. – cassandrad