Преобразование из зазубренного массива в двойной указатель в C#
-
23-08-2019 - |
Вопрос
Простой вопрос здесь: есть ли способ преобразовать из зазубренного массива в двойной указатель?
например, преобразование а double[][]
к double**
Этого нельзя сделать, просто к сожалению (как это может в простом старом C), к сожалению. Используя fixed
Заявление, похоже, тоже не решает проблему. Есть ли какой -нибудь (предпочтительно как можно более эффективным) способ достичь этого в C#? Я подозреваю, что решение может быть совсем не очень очевидным, хотя, тем не менее, я надеюсь на простой.
Решение
Двойной [] [] - это массив двойного [], а не двойного*, поэтому, чтобы получить двойной **, нам сначала нужен двойной* [
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
}
}
Я не могу не задаться вопросом, для чего это и есть, и есть ли лучшее решение, чем требуется двойной указатель.
Другие советы
Немного безопасности.
Как упомянуто в комментариях к первому решению, вложенные массивы могут быть перемещены, поэтому их также следует закрепить.
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.LANDATE метод
Во -первых, что 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));
}
}
}
}
}
}
Я на данный момент пошел с решением 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;
}
}