Немного безопасности.
Как упоминалось в комментариях к первому решению, вложенные массивы могут быть перемещены, поэтому их также нужно закрепить.
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](https://i.stack.imgur.com/WbCAD.png)
Теперь какой-то объект ушел из сферы и стал мусором, некоторые объекты только были выделены. Сборщик мусора перемещает старые предметы из кучи и перемещает другие объекты ближе к попрошайничеству или даже к будущему поколению кучи. Результат будет выглядеть так:
![enter image description here](https://i.stack.imgur.com/IM01A.png)
Итак, наша цель состоит в том, чтобы «шпилька» некоторые объекты в куче, так что они не будут двигаться. Что мы должны достичь этой цели? У нас есть заявление 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));
}
}
}
}
}
}
Я думаю, этот вопрос сводится к: вы можете конвертировать из одного указателя (в объект) в указатель на указатель? – zvolkov
Решение с использованием метода расширения ToPointer - это плохая идея, потому что тогда вы будете использовать указатель за пределами «фиксированной» области, до которого время выполнения .NET могло перемещать массив в другое место памяти. – Greg
Умный дизайн Microsoft должен был учитывать: «Исправить» базовое свойство объектов массива, а затем сделать массивы конвертируемыми в указатели (соответствующие C/C++) без необходимости странного фиксированного «утверждения». Таким образом, когда вы преобразовываете массив в указатель, как часть преобразования, массив автоматически «фиксирует» себя бесконечно (поскольку невозможно предсказать, как долго может быть указатель). Я не могу придумать какой-либо другой функциональный, безопасный способ поддержки поддержки указателей на управляемом языке ООП, и я лично считаю, что Microsoft была довольно недальновидна, чтобы пропустить это. – Giffyguy